Entitas
Entitas copied to clipboard
ECS Game Architecture with Unity and Entitas
ECS Game Architecture with Unity and Entitas
This is my mental model based on which I decide where to put my code. Feel free to comment so we can improve this model further
If I strictly apply this model to Match One, I'd have to remove the EmitInputSystem
and turn it to a MonoBehaviour that emit input entities.
I would have to remove the AddViewSystem
and the ViewComponent
and have a ViewController or ViewService that listens to AssetComponent
, then creates a view and binds it to an entity. The View would update itself with position listeners and destroyed listeners.
If I'm really strict I'd also remove the AssetComponent and have a mapping from entity type to asset name in the View Service, eg. isPlayer -> "Views/Player.prefab"
In my other games I'd also have to replace the PlayAudioSystem
with and AudioController or AudioService. There would be no audio context and no AudioComponent anymore.
How does this sound? Am I going crazy? :)
UI and Views visualize the current game state. They don't affect the game simulation. They can emit input like button clicks, but the game simulation doesn't know if this input came from a button, from the network or from some other program. That's why we don't need Entitas systems for them and can decouple them from the core logic as observers of the game state. UI and Views know how to represent the current game state. This allows us to start with placeholder assets which later can be replaced with the real game assets without touching the game logic.
We are doing it exactly like shown in the graphic. Furthermore we splitted the game logic into different parts: Action -> Command -> Core -> View. The View or other external inputs are able to create actions. The systems there are doing checks if it's possible and emit commands. The command systems will change the state of the core. Core systems only change core state. View is reacting on core and creating view state. Listeners are getting called, because something changed directly in core or in view. We also have a meta context which is just loaded balance values (from json just for easier access). This is done because action and view are part of unity (client side). Command and core are part of the simulation and will run not only on the client, but also on the server (we have a simulation game).
1st result of applying this strictly with TDD (Audio): Before:
- AudioComponent
- PlayAudioSystem
- AudioService
- no abstraction, dependency to unity
After:
- No Component / System / IListener / EventSystem
- AudioService (half the code)
- Abstraction (IAudioService, IAudioClip)
- Testable and no dependencies to Unity
Less complex and almost certainly more efficient
I guess I'm going down this road further :)
Can you post your experiments into some repository?
i thinks it's good to use react system to tell ui & view logic. by the way , sometimes good to use event + iListener to make it better right now. good !
Model looks nice and very intuitive, should be in wiki somewhere 😄
I wouldn't place Physics Collision
alongside Button Click
, because Unity runs physics in separate cycle N times before Update FixedUpdate > InternalPhysics > OnTriggerXXX > OnCollisionXXX > yield WaitForFixedUpdate
.
Bullet may bounce of wall during few physics cycles before it gets destroyed in Game Logic
, if we mark Bullet as trigger it gives no collisionInfo, and if we add BounceComponent
to Bullet marked as trigger, it will travel a bit inside objects before GameLogic
corrects its direction.
I also refactored my views now. All testable and very nice :)
- removed ViewComponent
- Removed all view systems
- Views updated themselves using the new Entitas events
- ViewService listsnes to asset event and creates view from object pool
- View reacts to destroyed and pushes itself back to the object pool
- Added IView and IViewInstantiator to wrap monobehaviours and view creation. This was a result of TDD with TestView impl and TestViewInstantiator impl. The game uses UnityView (wraps MonoBehaviour) and UnityViewInstantiator (loads from Resources but can be swapped with UnityAssetBundleViewInstantiator). This way you can simply swap from creating view from resources to creating views from asset bundles at a later point of time. Object pooling is in ViewService
I really start loving the new events. It feels so clean now :) (We introduced the idea in 2016, but now that we can generate it's even nicer)
Awesome. Btw @sschmid does it support loading some game objects first at loading time? For example, I want to load 10 enemies into game object pool first before starting game. I expect there is an API to preload the game object first into game object pool.
Yes :)
Updated diagram to include commands. External input should be checked first and if valid creates commands which affect the simulation.
Invalid input could be for example buying an item, but you don't have enough resources. Commands don't verify, they only act.
I recently built a few smaller games following the "Game Architecture with Entitas" diagram very strictly and I was really happy with the results. After the latest Video Tutorial about TDD #611 I started with strict testing again, and I have to tell you: TDD really had a big impact on my apis and the general outcome. I'm so much happier with the result now. The code is way more reusable, which I was aiming for to be able to reuse as much code as possible in the next game. The apis are much cleaner and I got rid of unnecessary complexity. Plus, It just feels so much better to see all the green passing tests :) Really happy now! TDD FTW!
As another consequence I could get rid of the following contexts: Input, UI, Audio. I only have game and command context left. Feels like everything becomes simpler 👍
ViewService, AudioService and all the other services are all less than 50 lines of code. Things became veeery simple now :)
This sounds very interesting. Will you be sharing your game examples using this structure?
I've been struggling a bit in my current project to convert input actions/intents into validated commands and that apply the state changes. I was trying to follow a design discussion you had on gitter a few months ago.
Would you do A.I. in the "External Input" section or "Game Logic" section? I was planning to do A.I. as Entitas systems just like player input. Each creature/NPC would create intent actions (just like input, eg. MoveFoward) to be validated and converted into Commands. I then run all command systems in the simulation step before render.
Which logic should be done directly in systems and which should be passed off to do in View's?
I don't understand why you'd want to move Input out of a system if it could exist in a system.
If it lives inside the "view/presenter" domain, i.e. in a Monobehaviour, you have much less control over it.
I generally try to keep as little as possibe in Monobehaviours. However, I also rarely use OnMouse... handlers, but rather roll my own input with raycasts and state etc., to account for multi-touch etc.
You can definitely have systems for input, no problem. I'm thinking in a very general and very broad context, one in where you can take the game logic as is without any modification and run it everywhere, e.g. server. On the server, probably don't run unity and also you don't have input like mouse. Only commands, e.g. as a result from validated network input. Same is true for views etc. I'm really reducing it down to the core logic. You can still have systems for input and views if you want, but then you'd have to modify the logic in a scenario where code is shared
What's also really helpful in our project is that we have an Action Context before Command Context. Like in the updated architecture commands are only verified data for the game logic which gets checked in action systems. But actions also help with view systems and the data in view. Actions are allowed to change the view context directly. That's very helpful for menues and other view stuff where you need reactive behaviour, because they are complex (best practice here is, that menues never have state in MonoBehaviour and only react to view components). So you don't have to make commands out of actions that are only interesting for the view and the game logic doesn't need to know about these. Therefore I would create a new rectangle before commands and call it action with a second line to view. (Could be to complex in the MatchOne example or Schmup, but in a bigger project this step is important imo)
There are lots of benefits when you have a defined architecture in your game. One of those benefits is that you can automate and generate code.
Imagine you want to add a new input action, like Shoot
. Sticking with the diagram above, you'd have to write and implement the following classes:
- InputAction.Shoot
- ShootInputActionService that validates the input and emits the shoot command
- ShootCommand
- ShootCommandSystem
You need to update the the CommandSystems list and the InputActions list.
I wrote a custom roslyn code generator that does all that for me. I only need to maintain a static class with fields (basically the input action types)
public const string START_ROUND = "StartRound";
[DataField(Data.POSITION_X, typeof(float))]
[DataField(Data.POSITION_Y, typeof(float))]
[CommandField("position", typeof(Vector3))]
[Event]
public const string AIM = "Aim";
[DataField(Data.POSITION_X, typeof(float))]
[DataField(Data.POSITION_Y, typeof(float))]
[CommandField("position", typeof(Vector3))]
[CommandField("velocity", typeof(Vector3))]
[CommandField("ammo", typeof(int))]
public const string SHOOT = "Shoot";
The code generator will then generate:
- updates InputActionsServices list
- generates ShootCommand
- generates partial ShootCommandSystem
- adds system to CommandSystems list
All I need to do is to implement the ShootInputService and fill out the rest of the partial ShootCommandSystem (which is only the execute method).
Very nice :) 👍
Would be nice to have a link to gist with the result of code generation. Otherwise there are too many questions in my head 😁
@sschmid Awesome. I hope I can get that new feature soon.
I wrote a custom roslyn code generator that does all that for me. I only need to maintain a static class with fields (basically the input action types)
@sschmid Can you integrate custom roslyn code generator into Entitas? So for those who need it can directly use it.
To write a custom roslyn generator you have to set up a new c# project with .NET 4.6 and install roslyn. If you reference DesperateDevs.Roslyn you can use some utility methods already. I will probably make a video about it. Maybe a good idea to also ship a working code generator project with Entitas 👍
Maybe a good idea to also ship a working code generator project with Entitas 👍
Awesome. It can be a very good starting point to learn how to write custom code gen as until now I still dunno how to do it. lol
I updated the diagram. Feedback very welcome
repost from @RomanZhu
In that diagram InputService validates input, checks amount of resources and creates command, then that command is executed without validation 150 resources - 100 (command1) = 50 resources; What if we call that InputService twice or more? It will create 2 commands, as we can't change resources from Service. It means there are some validations needed when executing commands? 150 resources - 100 (command1) - 100 (command2) = -50 resources
You are right. In my previous project I had sth similar as @StormRene described, an InputActionSystem in between the action and the command. This way you can do sanity checks like "is there only one command or more". I will probably re-add this to the diagram and my code, as this can be very useful.
Is a command just an entity with some components?
@cruiserkernan yes. E.g. entity with ShootCommandComponent added
Hi, design question, what would you say? (follow up of the problem described by @RomanZhu)
Given as described in the diagram:
- InputActionService validates input and creates command if valid
- Commands execute without validation
Problem:
- more than 1 BuyItem input action. The service checks
if (hasEnoughResources) {
createBuyItemCommand();
}
The BuyItemCommandSystem.Execute:
resources -= item.cost;
Since we execute the command system later and therefore subtract the resources later, both inputs are valid, even if we don't have enough resources to buy the 2nd item.
2 options I see:
-
Have a BuyItemInputActionSystem that can do sanity checks like, is there only 1 entity, or, do we have enough resources to buy all items of all input actions and then create the commands.
-
We change the definition and the command system can also do sanity checks and doesn't apply changes without validation.
With option 1 what we end up doing is basically having almost identical systems:
- BuyItem reactive systems that does the validation
- BuyItem command system that applies the changes
Seems really redundant somehow. What do you think?
Option 2 seems more redundant, but is also more flexible, since 1 input action can result in multiple different commands if needed
I would prefer solution of @StormRene with queue for all external inputs So only one action is served in a single tick
@RomanZhu I would say that's a game specific decision and doesn't apply for all scenarios. I'm currently updating my an example code to include the validation layer with reactive InputActionSystems to get a better feel for it.
I had same problem on start of my project and decided to drop actions at all, all my commands had validation (your option 2). Queue solution must work properly, it's very rare situation when you can get >1 action from user (gui/input) per tick. Option 1 is really redutant because there can be multiple types of command that need some spendable resource. I had 5-6 of such commands ;)
There is no best answer, better do what works for your in current situation, it's not that big of a deal to refactor it and if there are tests, you don't have to worry about it :)
I was thinking about making queue only for actions who are using resources (trading, eating, etc) and serving all other actions without queue (movement, etc)
I would say you could do the following: in your InputActionService
you could flag all commands that need to be queued with an entity.isQueuedCommand = true
flag.
class InputActionService {
//queued
public void BuySomething() {
var entity = commandContext.CreateEntity();
entity.AddBuyCommand(something);
entity.isQueuedCommand = true;
}
//continuous
public void MoveSomewhere() {
var entity = commandContext.CreateEntity();
entity.AddMoveCommand(somewhere);
entity.isContinuousCommand = true;
}
}
A QueueActiveSystem
flags only one command per tick from the queue as active so it can be processed and another deletes active && queued entities after the tick. With that approach you can solve queued and continuous commands easily. For better readability you could also create a entity.isContinuousCommand = true
flag and separate in two features: QueuedCommandsFeature
and ContinuousCommandsFeature
in your CommandsFeature
. (Option 2, Commands do sanity checks)
I've re-did what we've used in my previous project with some changes. Here's my current setup:
- InputController (MonoBehaviour) emits InputActionEntity.BuyItem
- BuyItemSystem (reactive) will do validation and eventually emit CommandEntity.BuyItem
- BuyItemCommandSystem applies changes
I removed InputActionService which is now replaced by individual systems.
All components, systems and systems wrapper are generated. So to add a new input action I add a field in this class:
public static class InputActions {
public const string START_ROUND = "StartRound";
[Event, DataField("lane", typeof(int))]
public const string JUMP = "Jump";
}
Thinking about generalizing the input action components to only 1 component
string inputAction;
Dictionary<string, string> data;
to be able to easily serialize and record inputs
Thinking about generalizing the input action components to only 1 component to be able to easily serialize and record inputs
Ya. It will make it also able to see all the input action with visual debugging.
That would already be possible now
Other inputs will have issues with multiple different command types also. For example if there were Move and PickupItem commands both coming from input actions in same frame. If move is done first, then you might be too far away to pickup item. If you pickup item first it might be trapped and paralyse player preventing movement.
I think it might be best to skip early validation. Just create commands (as intents) from any input and validate them on execute. Which ever happens first becomes the new state. The other commands will need to softly fail doing nothing and be skipped or apply slightly differently if possible to the new state.
In my project I had been working with commands late in the game loop, applying after regular game logic, but before render views are updated. This was so AI systems could run early just like player and network input and I could treat their decision as input for creating commands. Movement happens which can cause new views to be streamed in and old view streamed out. Some AI will potentially run from MonoBehaviour assets in that early External Input section and some from Game Logic systems.
Imo just one thing is missing: You don't want to always create actions and commands for changing the view/view logic systems. With the current diagram you could think it's a good idea to control a full fletched sim city menu via the game logic. I think there should be another part the View Logic as a own box between Game Logic and Observers and another line directly from Input Actions to View Logic.
View Logic is not unimportant. When you are creating listeners for directly game logic you are not able to process the data further for the view. Often you want more data that's only for the view. Let's say you have weapons with damage and fire rate, the view could need a damage/min info thats unneeded for game logic.
Here's the difference:
class WeaponView : MonoBehaviour, IGameDamageListener, IGameFirerateListener
public float damage;
public float fireRate;
public float damagePerMinute;
void OnEnable() {
listenerEntity.AddGameDamageListener(this);
listenerEntity.AddGameFirerateListener(this);
}
void GameDamageListener(float damage) {
this.damage = damage;
this.damagePerMinute = damage / ... //wtf i don't have firerate now.
}
void GameFirerateListener(float fireRate) {
this.fireRate = fireRate;
this.damagePerMinute = ... / fireRate //wtf i don't have damage now.
}
You don't know the order from the view perspective so you beginning to save the state of damage and fireRate and dividing if the divisor is not zero in both. Or start another query against game context. But that shouldn't be happening in the first place, because that's why you are using Events.
With View Logic it's easy. You can react as usual on changes of game data and creating specific view data like a ViewDamagePerMinuteComponent
. What you get is super clean MonoBehaviour Views to present.
class WeaponView : MonoBehaviour, IViewDamageListener, IViewFirerateListener, IViewDamagePerMinuteListener
public float damage;
public float fireRate;
public float damagePerMinute;
void OnEnable() {
listenerEntity.AddViewDamageListener(this);
listenerEntity.AddViewFirerateListener(this);
listenerEntity.AddViewDamagePerMinuteListener(this);
}
void ViewDamageListener(float damage) {
this.damage = damage;
}
void ViewFirerateListener(float fireRate) {
this.fireRate = fireRate;
}
void ViewDamagePerMinuteListener(float damagePerMinute) {
this.damagePerMinute= damagePerMinute;
}
Btw, until there's a way to automatically generate this, you can also create custom events that are based on multiple components.
In your case sth like IDamageFireRateListener
with (GameEntity entity, float damage, float fireRate)
I use sth like this already
Btw, until there's a way to automatically generate this, you can also create custom events that are based on multiple components
how?
good old manual coding :) no code generation
hi everyone, how to avoid this code?
[Buff, Effect, Actor]
public sealed class RemovedComponent : IComponent
{
}
public interface IRemoved : IEntity, IRemovedEntity { }
public partial class ActorEntity : IRemoved { }
public partial class EffectEntity : IRemoved { }
public partial class BuffEntity : IRemoved { }
public class RemovedSystem : MultiReactiveSystem<IRemoved, Contexts>, IInitializeSystem
{
public RemovedSystem(Contexts _contexts) : base(_contexts)
{
}
public void Initialize()
{
}
protected override void Execute(List<IRemoved> entities)
{
foreach(var e in entities)
{
IView view = null;
///////////////////////////////////////////////////////////////////
// because Actor and Effect has view component, but buff is not a viewable entity
if (e.contextInfo.name == "Actor")
{
var a = e as ActorEntity;
view = a.view.value;
}
else if (e.contextInfo.name == "Effect")
{
var eff = e as EffectEntity;
view = eff.view.value;
}
// any way to avoid?
///////////////////////////////////////////////////////////////////
if (view != null)
{
view.Unlink();
view.Hide();
}
e.Destroy();
}
}
thanks
I gutes I would introduce a getView()
method on IRemoved
interface and implement it in the partial class definitions. BuffEntity
would return null
other two, this.view.value
. Than you can just call IView view = e.getView();
and remove the block you marked.
@nomadfighter alternatively, you can try a general component check like this:
if (e.HasComponent(ActorComponentsLookup.View)) {
var view = (ViewComponent)e.GetComponent(ActorComponentsLookup.View);
}
The ids used to line up across contexts, but I'm not sure if that's still the case. You'd have to check if the View index is the same in all the generated lookups
Btw, with the new events it's simple, the DestroyedComponent
would have an [Event(true)]
attribute and the views are IDestroyedListeners
and can unlink and hide themselves
@sschmid Would you have time to update some Match-One branch to reflect your experiment? I'm not yet imagining all the details that you are experimenting with. For example you wrote:
Removed all view systems
On the master branch of Match-One there are view systems: https://github.com/sschmid/Match-One/tree/master/Assets/Sources/Systems/View
I am waiting for your sample project
@sschmid Where is the DestroyEntitiesSystem in your model? Currently I ended up splitting the Eventsystems into two systems, DestroyedEventSystem and the rest. Now my flow is: External Input ---> Input Actions ---> Commands ---> Call Destroy Events ---> Destroy Entities ---> Game Logic ---> Rest of the Events
The reason why I using this is to be able to cleanup everything before the game logic. Simple example: Restart game
- User create input with
RestartComponent
- Input actions triggered by
RestartComponent
and flag all entity withDestroyComponent
- Destroy events has sent to all listeners (including viewservice, so unity objects can go back to pool)
- Destroy all entities with
DestroyComponent
entities - Game Logic triggers
RestartComponent
and re-initializing all game systems. - New entities are created
- Send the other Events (viewservice can render now from pool)
With this flow I can cleanup and re-initialize the whole game in the frame of the input without any leak or garbage.
But maybe I miss something and you have a better implementation. Thanks.
Wanted to share the architecture we used in one of our game which is pretty similar to the diagram that Simon shared with us.
As an ‘entry’ point we have the GestureSystem which listens inputs from Unity and translates them meaningful gestures like TouchDown, TouchUp, DragStart, Drag, DragEnd, TouchHold, Pinch etc .. and provides additional data like touch position, hold time etc .. Also any UI input happens at this state.
Then we have ‘Controllers’ that listens to the gestures through an interface or events and they basically validate the inputs based on the state of the game and construct commands. Rarely controllers have a dependency to another controller to coordinate validation.
Commands of different type are sent to a CommandSystem that basically executes them. The command system contains blocks of command executors (one for each type of command) that tries to add entities with specific components to the game (the entity construction happens through a factory service) There might some few simple validation happening here, but most of the commands don’t care about the state of the game. Destruction/cleanup of game entities also happens here by calling specific commands.
When entities are created they enter the ‘Entitas’ loop where Systems only operate on entities with certain components and there is no validation.
There are also ‘Controllers’ that look at the game on each update and change the entities states by executing commands upon them (this is how AI/State machine is handled)
The view is mainly handled by ‘Controllers’ and a Data-binding service but there are cases where view listens to systems.
Services and utilities are injected with a DI framework to controllers/commands/systems.
I think that this is very close to what Simon shared and can be entirely in Entitas and I can say that this has scaled pretty good so far.
@sschmid Do u plan to release custom roslyn code generator soon?
I shared my interpretation in a wiki post: https://github.com/sschmid/Entitas-CSharp/wiki/How-I-build-games-with-Entitas-%28FNGGames%29
@FNGgames Thanks for wiki post. I really understand more on how important it is to have a good game architecture.
I have a question about combining unity and entitas. How would you make the PhysicsObject (the code can be found in https://unity3d.com/learn/tutorials/topics/2d-game-creation/scripting-collision?playlist=17093) into a PhysicsService and through entitas system update the view of the character?
@FNGgames Great post!
I want to get access to the collider bounds of a gameObject. Following your workflow would I create a ColliderComponent and access the value from there or create a service to access the collider bounds? Since I don't have a viewComponent anymore I can't access it through the view.
@YimingIsCOLD, @cruiserkernan I have kept hold of my ViewComponent for this reason. The IViewController interface acts as the base view, then I have things like IPhysicsController which have fields like mass, drag, and methods like ApplyForce(). In my view service I'm instantiating and then searching for scripts attached to the instantiated gameobject e.g.
var go = Instantiate(...);
var view = go.GetComponent<IViewController>();
if (view == null) return;
view.InitializeView(contexts, entity);
entity.ReplaceView(view);
var physics = go.GetComponent<IPhysicsController>();
if (physics != null) {
physics.InitializePhysics(contexts, entity);
entity.ReplacePhysics(physics);
}
You can put whatever you need in those interfaces. You only need a component if you want the ability for a system to be able to pull data from the view. In my case i have an execute system that loops through all the physics components and pulls position and velocity from them onto components - so Unity physics solves for position then i suck it back into entitias.
Every prefab spawned into my game goes through that so if you want your prefab to be a physics object or a collider object or whatever you just hang a script on it with that interface. You dont need special entity configuration code (my physics controller script has public editor variables i can set for mass and drag, these get added as components to the entity during InitializePhysics()). So configuration is just in the editor on the scripts.
So I just have to go _context.CreateEntity().AddAsset("Player")
and I get a fully configured player entity with all its necessary components set up according to the values in the scripts on the prefab. My player prefab looks like this:
https://i.imgur.com/i1WHH7g.png
So in my case I could have a IColliderController and have a ColliderComponent?
Sure - any functionality you need from unity you can get this way.
I tend to separate "ViewController" type things from "Service" type things along the lines of whether the information im looking for is global or local. E.g. Physics Service has raycast methods since these are stateless static methods. It doesn't need to live on a view gameobject to work. Physics Controller on the otherhand has things like mass / drag / apply force which all apply to a particular object instance.
But otherwise its the same - you are saying "what info do i need from unity?" and then writing interfaces that act as a pipeline to get that information into your game code. Thats why i draw the diagram with the interfaces half-in-half-out of the box, they are the information pipes. You get to choose exactly what pipes to build so you dont carry all the extra bloat that comes with the implentation in the game engine or the asset store thing youre using.
You can put whatever you need in those interfaces. You only need a component if you want the ability for a system to be able to pull data from the view. In my case i have an execute system that loops through all the physics components and pulls position and velocity from them onto components - so Unity physics solves for position then i suck it back into entitias.
@FNGgames Does your execute system runs on a fixed update to update the entity's position?
I would also say, the whole "logic talking to view layer == bad" thing is probably overblown. It's a game, it's safe to say that it's being drawn, but less safe to say how it's being drawn.
I don't mind having these systems that talk to views because they're only ever talking to my interfaces. I use events to minimise the amount i have to do this, but i find it really useful to have some references around in case i need to talk to them. That's not to say it's not a bit of a crutch, but i built these before Simon did events and I'm clinging to them for a bit longer :)
Does your execute system runs on a fixed update to update the entity's position?
yeah, i use the new non-alloc groups though so i dont notice the cost.
@FNGgames Thanks for your help!
@FNGgames I see. Thanks for the clarification.
@FNGgames What is your tag on UnityGameView for?
@cruiserkernan it's an enum tag i give to all entities - similar to unity's tag system, so i can find entities with tag or check a tag for something when i have a collision or whatever. It's just a component with an enum field.
@FNGgames
I don't mind having these systems that talk to views because they're only ever talking to my interfaces
That's exactly how I did before, too. If you think about, using the events, that's still exactly the same: a system will call a method that's implemented from an interface. The only difference is, these systems are now automatically generated and you don't have to write them + those systems work on all kinds of things, not only views.
@FNGgames Btw, thank you very much for your post. I love it :) Thanks for taking the time, I think it will help a lot of people who are new to all of this.
@sschmid good point RE: the events - there's still something talking to the views, but it's all hidden away.
No problem on the article, tbh i probably wrote it all in chat a couple of times before - it's nice to just be able to give some a link now :).
To answer a question from the chat:
My only question is about the Input -> Command -> Process. Is there an example of this because I have a hard time differentiating it from normal Inputs in the Input context then picked by Systems to be applied to Entities
This is an optional idea that some of use to streamline adding new features and inputs. Every game has a finite set of inputs like jump, shoot, buyItem, etc. This diagramm is a suggestion how to deal with those inputs. The input action layer is validating them, the command layer is applying them to the game logic. To be specific (Shoot example):
- You create a ShootComponent with 2 contexts: InputAction and Command
- InputController : MonoBehaviour creates new InputEntity with ShootComponent when mouseDown
- Reactive ShootInputActionSystem is validating the input (e.g. can shoot? hasAmmo? is bullet cooldown complete?) and creates CommandEntitiy with ShootComponent (or not)
- Reactive ShootCommandSystem does whatever needs to be done, e.g. create a new bullet entity that spawns in the game
the larger the game and the more inputs you have, the more important it becomes to have a defined way how to handle those inputs. This approach helped me managing this even with lots of inputs
I just want to make sure if I understand it correctly. This would mean I have an Input, Command and Game context version of the components? Then, I have an InputSystem to filter the Input entity which creates a Command Entity, then have a CommandSystem to operate on the Command entity which it applies to the Game entity?
see comments above https://github.com/sschmid/Entitas-CSharp/issues/610#issuecomment-367994419
Yes, that's the idea. Most of it can be generated as described in the comment above. So all I implement is just the execute of the input system and the command system. Everything else will be generated
I removed InputActionService which is now replaced by individual systems.
@sschmid I wonder how you can like emits InputActionEntity.BuyItem
from a UI that has MonoBehaviour since when linking entity to its MonoBehaviour, it will only pass Game Context? Have u use something like EntityService
in Match-One?
What do you think about physics interaction with ECS/Entitas? If we need to interact with the physics, where would you put the "logic"?
Here my example :
I have a plane controlled by physics (Rigidbody.AddForce) and a PID controller to calculate the right amount of force to apply.
-
Would you make a system to calculate the force and vector to apply to the rigidbody and a PID component to save some values between calculation? You need to know the current velocity too in the calculation... So, with the final force calculated, apply and trigger an event for the view to add that force to itself?
-
Would you make an event with the view listening the force rate needed to apply and the View/MonoBehaviour do the calculation with its PID controller and apply the calculated force on the rigidbody?
-
Would you put the rigidbody in a component and add the force directly from the system to that rigidbody, like the endless runner example?
With various Entitas examples, I see that the views listen the position component to update its transform values, because it easy to see the position of the view as a state of the view. But with physics, like the velocity, is it a view state controlled by the rendering engine or is it part of the game logic and it could be unit tested?
@OmiCron07 I'd say it's up to the developer to decide. There is a balance what data to put into ECS layer and what to keep in View layer.
Physics is a special case, cause FixedUpdate
runs right before Update
[0..N] times (Unity Events Order, Physics.Simulate).
I'd handle most of physics interaction inside View layer(or create FixedSystems to run in FixedUpdate). And occasionally send data through ECS components so that systems and eventSystems could react.
- Would you make a system to calculate the force and vector to apply to the rigidbody and a PID component to save some values between calculation? You need to know the current velocity too in the calculation... So, with the final force calculated, apply and trigger an event for the view to add that force to itself?
Sounds ok to create system to calculate something and another system to apply calculations.
- Would you make an event with the view listening the force rate needed to apply and the View/MonoBehaviour do the calculation with its PID controller and apply the calculated force on the rigidbody?
It would work, could be tempting and easier to make but sounds more fragile in the long run.
- Would you put the rigidbody in a component and add the force directly from the system to that rigidbody, like the endless runner example?
Often ViewComponent
is already attached, it's fine to add/access other MonoBehaviours through entity components. Unless you have some architecture restriction on using monoBehaviours in systems
- With various Entitas examples, I see that the views listen the position component to update its transform values, because it easy to see the position of the view as a state of the view. But with physics, like the velocity, is it a view state controlled by the rendering engine or is it part of the game logic and it could be unit tested?
Physics changes unity transform values. It's possible to sync changes afterwards by checking Transform.hasChanged
or some other trick. Or keep all transform calculations inside unity View layer and use unity values inside systems
Thank you for the responses, will check it out how it turns out.