Feb 3, 2025

Cartograph

Solo Project
December 2024 - Current
Skills Utilized: C++, Unreal Engine, Concurrent Programming, Multiplayer Programming

Introduction

I really enjoyed playing Satisfactory, but I always thought it would be nice to see all my buildings I built on the map. This led me to create a mod, Cartograph, using the excellent framework, Satisfactory Mod Loader (SML).

I leveraged the speed of rendering textures and shapes on a render target to draw buildings on top of the existing map. To improve user experience, I added features such as toggling building visibility and filtering by height, seamlessly integrating with the base game.

Since buildings can number in the thousands, I optimized the mod by spreading the rendering across multiple frames and only redrawing the affected sections when buildings are placed or dismantled. Additionally, I made it to fully support multiplayer.

At the time of writing, the mod has reached over 18,000 downloads and has the highest conversion rate among mods, with a view-to-download ratio of about 4:1.

Satisfactory Mod Repository (Where the mod is posted): https://ficsit.app/mod/Cartograph

Showcase

↑ The game's map with Cartograph. It draws the buildings, with a menu for toggling each building and filtering by height.

↑ The game's map without Cartograph. It doesn't draw any building.

Technical Highlights

Distributing Work Across Frames

Processing building data and rendering it on the map needed to be done asynchronously to avoid disrupting normal gameplay. For that, I wanted a method that allowed pausing midway while retaining local state, making coroutines an ideal choice. Using UE5Coro, I structured the rendering process to keep a per-frame time budget while maintaining a manageable code.

Partial Redraw Optimization

When buildings were placed or removed, redrawing only the affected areas was necessary for efficiency. To determine which buildings overlapped efficiently, I used a quadtree to store building data. However, simply redrawing those buildings led to misalignment at the boundaries. To solve this, I applied scissoring while drawing buildings on the render texture. Since Unreal Engine’s render target draw functions didn’t support scissoring, I created a custom class inheriting from FCanvasBaseRenderItem and manually called RHICmdList.SetScissorRect in the Render function.

Multiplayer Support

In multiplayer, clients only receive data for buildings near them for optimization, but the map needs to render all buildings. To support this, I had to implement a custom synchronization system. A major challenge was the large packet size required to send all building data at once. To resolve this, I split the data into smaller chunks and implemented a back-and-forth communication system between the client and server to receive the data over multiple packets.

Conclusion

Through this project, I gained experience in asynchronous rendering, spatial data structures, and network synchronization. Implementing coroutine-based rendering improved my understanding of managing time-constrained tasks efficiently. Working with quadtrees and custom rendering logic deepened my knowledge of optimizing large-scale dynamic rendering. Additionally, handling multiplayer data synchronization reinforced my skills in packet management and client-server communication. This project provided valuable insights into optimizing both performance and usability in a real-time environment.

No comments:

Post a Comment