May 5, 2017
Devblog #003 Entity Component Modding
‘Sup folks. Medieval Engineers programmer Daniel Ilha here to let you know some more about this exciting new modding feature, which is the ability to write custom entity components, and integrate user-provided types into the engine in general. ‘Bout Me I’ve been working at Keen SWH since 2015. I have been involved in much of
‘Sup folks. Medieval Engineers programmer Daniel Ilha here to let you know some more about this exciting new modding feature, which is the ability to write custom entity components, and integrate user-provided types into the engine in general.
‘Bout Me
I’ve been working at Keen SWH since 2015. I have been involved in much of the tech behind Space planets, Medieval planets, optimizations and general back-end stuff. I find great joy in working on Medieval Engineers because of how enthusiastic and at the same time relaxed our team is.
Intro
Ok, first let’s have a brief overview of what entities and entity components actually are.
An entity is any object in the game scene the player may interact with. Most of these objects have a one to one relation with how the player perceives them (i.e a single rock or timber on the ground is a single entity). Yet some, such as trees, are perceived as several objects but are actually a single entity (all trees, plants, gatherables in an area are part of a single ‘environment sector’ entity).
In the following screenshot, we have highlighted entities in a game scene. Each entity is identified by a unique Id.
Some entities in the game have specific types (grids, the terrain, player characters, etc) and while it may seem quite natural to express that as a traditional inheritance tree, it soon becomes clear that many of the behaviors of each entity are shared. For instance, both players and blocks can have inventories and be damaged, most entities have both visual and physical representations. All objects are in the scene under some hierarchy (the scene graph). And while it may be simple to simply include all common behaviors in the base entity class this approach quickly spirals out of control and creates a maintainability hell.
This is where components enter, the idea is simple, we make an entity a collection of behaviors (which is what we call components), each behavior focuses exclusively on its own functions and only the other behaviors it has to communicate with. As a result, it is much easier to compose entities with exactly the functionality they require and no additional overheads.
Metadata
Now naturally we would love to have modders be able to take advantage of these systems so they can introduce their own functionality. Yet there were some very specific challenges to implementing that, and it stems from one of the most fundamental features of the VRage engine: the concept of type metadata tracking.
Essentially we make very heavy usage of key attributes which tell the engine how to use a type. And in order to index that information at any moment, we essentially scan all of the code base when the process starts for these special attributes.
Here is one short snippet of what it looks like:
[MyComponent(typeof(MyOB_FooComponent)) public class MyFooComponent : MyEntityComponent { [Update(100)] // Every 100 ms public void Update(long msSinceLastUpdate) { ... } ... }
And scanning user provided code for those attributes is not a problem, the main issue is that once these types are loaded and indexed we previously could not unload them. This would, of course, prevent the user from ever starting a new session without restarting the whole game.
Short story long, that is no longer an issue. Metadata is now loaded and managed in a hierarchical fashion, meaning all metadata for user types is stored separately and simply discarded when the session restarts.
So what are we getting?
So now we know what and why, but how? Well, the fullness of it is well beyond the scope of this hopefully short blog post. But let’s go over the process in broader terms.
The game is programmed in a rather data-driven way, and components are no exception. As a result, every component is composed of a Definition, which is used to configure the component for a particular use, and the actual component class itself.
In the example above, we rather briefly, showed how a component class looks. The associated definition is usually something like:
[MyDefinitionType(typeof(MyOB_FooComponentDefinition)) public class MyFooComponentDefinition : MyEntityComponentDefinition { public int Integer; public float Real; protected Override void Init(MyOb_DefinitionBase builder){ base.Init(builder); var ob = (MyOB_FooComponentDefinition) builder; Integer = ob.Integer; Real = ob.Real; } }
The definition is accompanied by its object builder, which is the serializable representation of the same. We use two objects so we can have the most user-friendly serialized state while also being able to have fully post-processed, validated, and ready to use data during gameplay.
With both definition and component code, we can add it to any entity in the world via container definitions. Each entity has a definition ID, by defining a matching Component Container we can attach any number of components to any occurrence of that entity in the game.
Bonus Nachos: Multiplayer
In addition to the goodies above another feature that was added is the ability to implement RPC multiplayer events in exactly the same fashion as we do. The system simply relies on a set of relevant annotations which mark object methods as callable.
Here is a quick example:
[Event, Server, Reliable] public void OnClientRequestFoo(int i, float f, double p) { // Validate client data, always, don't trust clients! DoStuffPostValidate(i, f, p); }
The snippet demonstrates a simple server method callable from the client. The attributes specify that the method is callable (Event), must be called from client to server (Server) and is sent reliably (Reliable).
In client code this method can be invoked with the following syntax:
// Ask server to do Foo MyAPIGateway.Multiplayer.RaiseEvent(this, x => x.OnClientRequestFoo, 42, 3.1415, 2.71828182845904523536028747135266249775724709369995);
The above method uses the magic of overload substitution to produce a type safe call and manages to marshal all the arguments without boxing. The above delegate is also cached so the call is quite fast. But it does require the delegate be cacheable, i.e. it may have any references to the calling context.
Now the above example is just a taste, the full API includes full support for broadcasts and end to end messaging. Also, there are some considerations that were left out, in either case, lookout for the complete mod guides when they become available.
Wrapping it Up
With these two main features, it is possible to achieve much higher complexity and quality of user generated content. It is by no means final though, we will continue to improve the APIs available in the coming weeks and months.
I hope you have enjoyed this short blog post.