Entity-Component-System Conventions
Prototypes
Prototype data-fields
Don’t cache prototypes, use prototypeManager to index them when they are needed. You can store them by their ID. When using data-fields that involve prototype ID strings, use ProtoId
[DataField]
public List<ProtoId<ExamplePrototype>> ExampleTypes = new();
EntityUid in Logs
When using EntityUid in admin logs, use the IEntityManager.ToPrettyString(EntityUid) method.
Admin log with entities example (click to expand)
// If you're in an entity system...
_adminLogs.Add(LogType.MyLog, LogImpact.Medium, $"{ToPrettyString(uid)} did something!");
// If you're not in an entity system...
_adminLogs.Add(LogType.MyLog, LogImpact.Medium, $"{entityManager.ToPrettyString(uid)} did something!");
Optional Entities
If you need to pass “optional” entities around, you should use a nullable EntityUid for this.
Never use EntityUid.Invalid to denote the absence of EntityUid, always use null and nullability so we have compile-time checks.
e.g. EntityUid? uid
Components
Component data access modifiers
All data in components should be public.
Component property setters
You may not have setters with any logic whatsoever in properties. Instead, you should create a setter method in your entity system, and apply the [Friend(...)] attribute to the component so only that system can modify it.
Your component may use properties with setter logic for ViewVariables integration (until we have a better system for that).
Component access restrictions
The [Access(...)] attribute allows you to specify which types can read or modify data in your class, while prohibiting every other type from modifying it.
Components should specify their access restrictions whenever possible, usually only allowing the entity systems that wrap them to modify their data.
Shared Component inheritance
If a shared component is inherited by server and client-side counterparts, it should be marked as abstract.
Entity Systems
Game logic
Game logic should always go in entity systems, not components. Components should only hold data.
Proxy Methods
When possible, try using the EntitySystem proxy methods instead of using the EntityManager property.
Examples (click to expand)
// Without proxy methods...
EntityManager.GetComponent<MetaDataComponent>(uid).EntityName;
// With proxy methods
Name(uid);
// Without proxy methods...
EntityManager.GetComponent<TransformComponent>(uid).Coordinates;
// With proxy methods
Transform(uid).Coordinates;
Events
Method Events vs Entity System Methods
Method Events are events that you raise when you want to perform a certain action. Example:
// This would change the damage on the entity by 10.
RaiseLocalEvent(uid, new ChangeDamageEvent(10));
On the other hand, Entity System Methods are methods you call on systems to perform an action.
// This would change the damage on the entity by 10.
EntitySystem.Get<DamageableSystem>().ChangeDamage(uid, 10);
Method Events are prohibited, always use Entity System Methods instead. There’s an exception to this, however.
You may use Method Events as long as they’re wrapped by an Entity System Method.
In the example above, this would mean that DamageableSystem.ChangeDamage() would internally raise the ChangeDamageEvent, which would then by handled by any subscriptors…
Ensure events are unsubscribed from when systems are shutdown. Proxy methods like Subs.CVar()or SubscribeLocalEvent already take care of it, note that you do not need to unsubscribed inside managers, as their lifetime ensures that when they shutdown, the rest of the client / server is also shutting down, making unsubscribing not necessary.
Event naming
-
Always suffix your events with
Event. Example:DamagedEvent,AnchorAttemptEvent… -
Always name your event handler like this:
OnXEventExample:OnDamagedEvent,OnAnchorAttemptEvent…
Struct by-ref events
Events should always be structs, not classes, and should always be raised by ref. If possible it should also be readonly if applicable. They should also have the [ByRefEvent] attribute.
In practice this will look like the following:
var ev = new MyEvent();
RaiseLocalEvent(ref ev);