If you recall, I mentioned last week that I was having problems with continuous async pathfinding and following, because if the Move To task was called on tick, the agent would continuous spawn an async task and abort it for the new task, leading to no pathfinding at all. I solved this by making sure it was done only if the target had changed and made sure to safely abort the current async task to use its resources for the new one. Obviously it wasn’t as simple as it sounds, I had trouble cancelling the current async task, it was crashing due to an assert inside the cancel function, which verified if the task was part of queue, so I had to step through the library’s code to see why it wasn’t letting the task go, even when the work was already done. My first approach was to synchronize the task, which completed the work if it wasn’t done and released the task, but that introduced another problem, if I had to synchronize the task to cancel it, I basically wasn’t really any cancelling, since it did the work anyway, which could be very expensive, and ultimately was equivalent to a synchronous call. Therefore, I had to come up with a workaround while I figure out why the queue is not letting me cancel the task, I am basically keeping an array of tasks while still having the current task, since I can’t reuse the tasks I instantiate a new one that runs on the async task library. All tasks are then deleted when the agent is destroyed, usually when the game ends. This is not ideal but works for now, and the improvement is quite noticeable as I no longer get blocked during pathfinding while moving through the environment in the demo. You can watch a comparison of the synchronous pathfinding (first) vs. asynchronous one (second) below.
The other part of this week’s progress is the serialization of the SVO data so it can be baked and reused as many times as needed, instead of rebuilding the whole tree every time in game. I have to admit this has been quite useful since now I don’t have to wait for the generation every time I need to test something in the map, I probably should have done this earlier, it really helps save on testing time.
Before getting on the serialization itself, I wanted to talk a little bit about some features I added to the editor details of the SVO volume, I created some custom buttons for the user to use for baking and clearing the SVO data. For this, I had to learn how to actually modify the editor details for any kind of actor, since it is something rarely used, there is little documentation, but there is definitely some. I added a new module to the plugin, this was a bit tricky since there is not an actual option in the engine to add a new module to a plugin, so what you have to do is manually added it to the .uplugin file like the main module and then add the files it would generate as it were a new plugin. This way, the engine will recognize the new module and add it to the solution when you rebuild these files. Once you have the new module, you just make it an editor module and create a class to customize the details of another class. All the buttons do is just call functions inside the volume.
To achieve serialization, you can use the serialization system already built into actors. I didn’t know how it worked, but now I do, any member variable marked as an UPROPERTY will be serialized since it needs to persists after the engine is closed to be used when the engine starts again. Therefore, these properties are already being serialized, but, I can’t simply mark as UPROPERTY the SVO data, since it is not public it can’t be exposed to the editor. That doesn’t mean we can’t serialize though, if you override the serialize function in your custom class, you can define custom serialization for you custom properties, so I do that. Additionally, if you need to serialize custom types, you have to create an overload for the << operator that takes in the custom type.
FORCEINLINE FArchive& operator<<(FArchive& Ar, TDPNodeLink& link)
{
Ar.Serialize(&link, sizeof(TDPNodeLink));
return Ar;
}
The above code snippet shows one of the overloads I created for supporting serialization of my custom types. Typically, if you have multiple data members, then you would serialize those as well.
Whatever you serialize on the function override will be stored on disk when you make changes to the properties in the editor and read when you start up the engine again. Remember that the data needs to be baked again if there are any changes in the generation settings or in the environment.
With this, we go into the final weeks of this project, I will start focusing on the dynamic SVO generation, so we can have moving obstacles in the environment, which is definitely not trivial and I will have to find various ways to reduce the problem space, so the complexity decreases but the results are still acceptable. That and setting up a nice demo for all these features will be the last epics for this project, I hope time goes easy on me for all the work that still needs to be done. See you next week!