-
Notifications
You must be signed in to change notification settings - Fork 31
Systems and Constraints
Systems contain your game's logic, previously written in a MonoBehavior's Start()
or Update()
methods.
Systems SHOULD NEVER store any data, i.e. no variables or constants. They can only have Start()
, Update()
, FixedUpdate()
, Event Handler methods, and helper methods.
To create a System, create a new class deriving from EgoSystem
:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem
{
//...
}
Like with typical Unity3D Components, you can write initialization code in Start()
, update your GameObjects in Update()
, and update physics objects in FixedUpdate()
:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem
{
public override void Start()
{
// ...
}
public override void Update()
{
// ...
}
public override void FixedUpdate()
{
// ...
}
}
Unlike regular MonoBehaviors, you need to override Start()
, Update()
and FixedUpdate()
. This helps to prevent the dreaded "I wrote update()
instead of Update()
" bug.
Systems can update any and all GameObjects with the desired Components through its constraint
member. You define the Constraint with the Type Parameter of an EgoSystem
:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoConstraint< ... >
>{
//EgoConstraint< ... > constraint;
}
You then set the desired Components for a Constraint in an EgoConstraint
's Type Parameters:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoConstraint< Transform, Rigidbody, Example >
>{
//EgoConstraint< Transform, Rigidbody, Example > constraint;
}
Here, ExampleSystem
's Constraint selects any and all GameObjects with a Transform, Rigidbody, and Example Component.
EgoConstraint
s automatically cache the attached EgoComponent
and desired Components for every relevant GameObject. EgoConstraints
by default support up to 12 Components.
Constraints have a ForEachGameObject()
method. It lets Systems iterate over each relevant GameObject (in no guaranteed order):
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoConstraint<Transform, Rigidbody, Example>
>{
public override void Update()
{
constraint.ForEachGameObject(...);
}
}
ForEachGameObject()
has one void callback argument, which is invoked for each relevant GameObject. The callback's first argument must be an EgoComponent
, and the following arguments must match the Constraint's desired Components. You can pass in a method reference, but passing in a lambda is the suggested style:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoConstraint<Transform, Rigidbody, Example>
>{
public override void Update()
{
constraint.ForEachGameObject( ( egoComponent, transform, rigidbody, example ) =>
{
//...
} );
}
}
You can safely call ForEachGameObject()
anywhere in a System: Start()
, Update()
, FixedUpdate()
, Event Handler methods, and Helper methods.
NOTE: It is a severe anti-pattern to nest ForEachGameObject()
calls on the same constraint.
An EgoParentConstraint
is an extension of a regular EgoConstraint
that defines what Components a GameObject's parent must have. You set the parent GameObject's desired Components as the EgoParentConstraint
initial Type Parameters:
EgoParentConstraint< Transform, Rigidbody, ParentExample, ... >
The final Type Parameter of an EgoParentConstraint
must be a fully defined child Constraint:
EgoParentConstraint< ParentExample,
EgoConstraint< ChildExample >
>
Above, these Constraints only select GameObjects with ChildExample
Components, and whose parent GameObject (where it exists) has a ParentExample
Component. If the child nor parent meet these requirements, the Constraints will ignore them.
You can nest EgoParentConstraint
s indefinitely, but the final innermost Constraint must be an EgoConstraint
:
EgoParentConstraint< ... ,
EgoParentConstraint< ... ,
EgoParentConstraint< ... ,
EgoConstraint< ... >
>
>
>
When calling ForEachGameObject()
on an EgoParentConstraint
, the callback must accept the parent's EgoComponent
, the parent's desired Components, and the child constraint:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoParentConstraint< Transform,
EgoConstraint< Transform >
>
>{
public override void Update()
{
constraint.ForEachGameObject( ( egoComponent, transform, childConstraint ) =>
{
//...
} );
}
}
You can then call ForEachGameObject()
on the childConstraint
:
// ExampleSystem.cs
using UnityEngine;
public class ExampleSystem : EgoSystem<
EgoParentConstraint< Transform,
EgoConstraint< Transform >
>
>{
public override void Update()
{
constraint.ForEachGameObject( ( egoComponent, transform, childConstraint ) =>
{
childConstraint.ForEachGameObject( ( childEgoComponent, childTransform ) =>
{
//...
} );
} );
}
}
NOTE: Use EgoParentConstraint
s, and especially nested EgoParentConstraint
s, sparingly. They are best used when describing a hierarchy of GameObjects is necessary, such as UI. If a System's logic can be adaquetely expressed with a simple EgoConstraint
, please do so.