Arma Reforger is the latest Arma game released by Bohemia Interactive. This is more of a demo of the Enfusion Engine than a fully-featured gaming experience. It has a similar scripting experience to that found in DayZ Standalone, with some markable improvements.
Reforger brings two new exposed script APIs that can accomplish some heavy-lifting if used well. First is MeshObject.Create().
Next is Physics.CreateStaticEx() and Physics.CreateDynamicEx().
These two functions allow us to create 3D meshes through code and apply physics and collision to them. They do not, however, replicate over the network. Only the machine that runs these commands can see or interact with the object.
We will design a system to replicate changes to the mesh over the network. The approach I will show is underdeveloped and has a lot of room for improvement.
First, let’s talk about generating the mesh and collider. To make this post simpler, I will use the Generic Box Entity shipped with Reforger.
The Generate method takes a provided size (in each axis) and generates a cube based on those bounds. L32-L39 defines the actual vertices of the cube, whereas L41-L49 defines the vertices to feed into MeshObject.Create. The indices on L51-L59 represent the triangles of the mesh. Each number corresponds to a 0-based index in the verts array. Every 3 values in the indices array make a triangle. The uvs variable on L60-L68 tells the mesh what part of the texture to render.
At the bottom of Generate, we feed our data into MeshObject.Create. There are some hard limits on array sizes, so if you need to generate a large mesh, the Create routine allows you to pass multiple Mesh definitions at once (the engine itself breaks large objects into a series of small meshes as well).
Adding collision to the object sounds complex, but the logic is straightforward. We are feeding the same values we created for the Mesh into the Physics Engine using PhysicsGeomDef.CreateTriMesh. There are other types of geometry we can generate for colliders as well.
First, we must destroy the existing physics object attached to the entity. Next, we construct a PhysicsGeomDef with PhysicsGeom.CreateTriMesh generated from our same verts and indices as our MeshObject. Lastly, we connect the physics geometry to the entity using Physics.CreateStaticEx or Physics.CreateDynamicEx. That will look something like the code shown below, where this is the entity we want to attach a collider to.
So generating a mesh and collider can be done through code, but this code is not networked. If we run this on the server, only the server will know the collider or mesh. We need to replicate this data to all current and future clients to make them render the mesh and collider. This is done through RplProp and the replication codec methods.
Starting off, I want you to read over the RplTestEntity defined in the base game. This entity showcases RplProp to the fullest extent I could find. It also includes the Codec methods necessary to replicate an object structure. The replication of mesh data will make use of these techniques.
Now we’ll define a very rough structure to hold our object’s mesh data. I used my Procedural Worlds mod for this example.
This object holds all the minimum requirements to define a mesh object in Enfusion. We will assume the physical and texture materials are unchanging. The codec methods define loading and unloading these field values from a serialized buffer. I implore you to read this code thoroughly, as there are ways to optimize it further for smaller and larger meshes.
All that is left is to define a scripted entity that implements the dynamic mesh and uses a RplComponent to render that mesh on all machines. I quickly modified the BaseChunkEntity from the Procedural Worlds mod to create a base class for these types of entities.
Any objects derived from this base class should override Generate and update m_MeshData with the mesh information. Once the routine completes, the mesh data will be replicated to all clients, and OnMeshDataUpdate will run to generate the MeshObject and Physics on every machine. Any entity derived from this class will need a RplComponent to operate.
With this, the Authority can modify the mesh as it chooses, and all Proxies will receive the changes and rerender the mesh.
Anyway, that’s pretty much it. As I said earlier, there are ways to optimize this to reduce network overhead and improve the replicated mesh quality.