What did i focus on
This is the second game that we developed using our engine, and as a result it is a lot more stable. Given that we already had a functional engine, I have been able to focus a lot more on optimizing and upgrading the previous systems.
When we started this project, I wanted to address the issues with the previous serialization workflow.
The main one being that if a component is modified from the version that was saved when the scene was exported. Then that component wont does not load. This resulted in a lot of frustrations as level designers were constantly asked to re-export scenes when programmers modified the functionality of a component. Additionally, the loading of a component was entirely binary, meaning that we had to use char instead of std::string, etc. While functional, I wanted the new system to address these issues, as it would speed up iteration times during our final two games.
a component from the old system
My plan was to modify the previous system to use identifiers to tell the code generator what it should do with the current variable. So that you can mark a variable to be serialized, or what it should do in unity, when exported.
The new component
I started by adding a macro identifier for struct, I decided to use S_STRUCT a shortened version of “Serialized Struct” as its self-evident what it does when you know what it stands for. As well as adding a Serialized macro, that expands to nothing, to allow tagging variables with whatever we wanted. Having the interface determined I started to re-write some of the code generator and component exporter to listen to S_STRUCT and Serialized identifiers. This was not a large change from the previous version, so I won’t go into details here. The bigger change however was that the code generator now generates code for each of the variables in a type, the generated code looks as such:
Auto generated code
Where the ADD_DESERIALIZE_FUNC would call the GetDeserializeFunctions for the variable type, allowing for custom types to be used inside of the components. ADD_DESERIALIZE_FUNC creates a lambda that calls the templated binary de-serializer on the variable.
As it generates a function per variable, this allows for non-binary conformant types to be used, as custom de-serializers can be implemented to handle special cases. Allowing us to load strings, entity references, and more, just by adding more custom de-serializers. As the de-serializers are wrapped in constexpr if statements its very optimized as most of the code gets optimized away.
Special binary de-Serialize case example.
A basic script
During most of the pre-production and alpha I spent a lot of time implementing a node editor in our engine. The goal with the node editor was to move the workload away from programmers and allow Level designers to create their own special events without the need of a programmer to help them
My implementation is fairly basic, since we got a simple node editor from the school, I was quickly able to upgrade the code and add a lot of features. Here are some of them:
Dynamic & Color-coded types.
As we wanted one node to be useable for multiple types, I decided to implement a system where you can choose the type you wish to use. This is automatically set when you connect the node to another.
We also decided to color code the different types based on a similar scheme that unreal uses for their node system, this was to lower the barrier of entry for our Level designers
As our scripting system runs on a per entity basis, we wanted the ability to have differing parameters between them. So, I implemented a variable system that uses a local / shared system.
Local variables are loaded from the scene and are local to the entity which the scrip is on.
Shared variables contain the values that get set from the script editor and are shared between all the scripts of that type.
One of my focus areas for the rendering was to try and replicate the basic feeling of Ratchet & Clank but in a more abandoned world. As i result i implemented and improved a lot of previous versions of our shaders
One of the more difficult tasks that i worked on was getting shadows to both look good and be optimized. I implemented shadows using vogel disk sampling and gradient noise to smooth out the harder edges.
When rendering we render the baked shadows into the G-Buffer, as the actual baked shadow textures are fairly small we split the rendering up the baked light cells. This allows for far reaching good looking shadows.
God rays are used to mainly increase the feeling of the air being thick. I decided to implement the effect using a screenspace shader as I wanted the effect the be automatically applied to all of our levels without needing custom meshes to be placed in all of our skylights to have a custom mesh on them.
God rays are used to mainly increase the feeling of the air being thick. I decided to implement the effect using a screen-space shader as I wanted the effect the be automatically applied to all of our levels without needing custom meshes to be placed in all of our skylights.
Going into this project we had a functioning, but bad PBR shader. And given that our new style needs good roughness and metalness rendering we had to fix it.
While not perfect, I worked towards getting closer to the way that substance painter looks as that is the tool that our Graphical artists use to design the materials on our assets.