top of page

Introduction

Play as an astronaut cat flying through space but with a twist! While cruising past an unknown planet, your ship is unexpectedly hit with a beam of energy, forcing you to make an emergency landing and scattering all your toys across the planet. As you crash into the surface, you must jump past the planet's dangers to gather your toys to repair your ship. Only then can you make it up a mountain to reach your newly repaired ship and escape the hostile planet to return home.​

​​​

Catstronaut was my sophomore-year game project at DigiPen, developed in a custom-built game engine by a team of 12. I served as a deserialization and tools programmer for the project. The game is a collectathon platformer where players must gather all items in a level to escape the planet they're stranded on.

​

To ensure full game serialization and streamline development for designers and artists, I built a pipeline using RapidJSON for game object serialization and deserialization. Additionally, I addressed a severe RAM usage issue by implementing an ECS-inspired resource management system, allowing multiple game objects to share a single sprite instance in memory. This optimization reduced RAM usage by 70%.

Deserialization Pipeline

In Catstronaut, one of my key responsibilities was building a deserialization pipeline using RapidJSON to load and instantiate game objects from external JSON files. This system became the backbone of our content workflow and allowed designers to modify and place objects in the world without ever needing to write of modify C++ code directly.

​

The system was centered around a JSON schema that represented object archetypes. These archetypes defined a game objects structure, listing all the components that it should include and their associated parameters.

​

Here's a simplified example of a game object archetype defined in JSON:

Upon loading a scene, the deserializer would parse these JSON files using RapidJSON and convert the raw data into generic RapidJSON objects. This data was then passed to a factory system that handled the actual creation of game object based on component presence.

​

The following snippet shows how JSON data is parsed using RapidJSON and passed into the factory system:

Each component type, such as Transform, Sprite, Player Controller, etc.—was registered with the factory system, which acted as a dispatcher. It would scan the parsed JSON data, check which components were present, and create the appropriate C++ component instances with the values populated from the file. 

​

Below is an excerpt from that factory function that checks for components and creates them accordingly:

This allowed for high modularity; adding new component types or modifying existing object archetypes was simple and did not require changes to the deserialization logic. Additionally, the factory could be extended to support new component types through a plug-in pattern, making the system future-proof for expanding gameplay features.

​

This architecture enabled a data-driven development workflow. Designers could save their in-editor object configurations directly to JSON, load them back in, and see changes reflected instantly without touching code. As the project evolved, this system proved essential for rapid iteration and made collaboration between designers and programmers more efficient. It also kept the engine lean, as only the necessary game objects and resources were instantiated per scene.

Resource Manager

During Catstronaut's development, we faced a serious memory issue: RAM usage was spiking to over 3GB, which was unacceptable for a 2D game of its scope. After some investigation, I discovered that the root cause was repeated loading of identical image files by different game objects. Each object was instantiating a new copy of the same sprite, even when dozens of the objects were using the same asset. To address this, I built a lightweight, ECS-inspired resource manager that centralized asset storage and dramatically reduced memory usage.

​

The system was designed around a shared storage model using an unorderd_map that indexed loaded resources by string IDs. This map allowed us to reuse already-loaded assets rather than creating duplicates in memory.

Here's is the internal storage structure used for the sprite assets:
​

While it was primarily developed to manage sprite data, since image files were the largest contributors to memory usage, it was built with scalability in mind. The system was built so that as the engine grew more assets such as audio could be made to work using the resource manager.

​

The resource manager was inspired by ECS design principles, especially in its used of ID-based access and centralized, decoupled storage. Game objects did not directly own their resources, they simply held string IDs, and the manager served as the source. This mimics how ECS stores component types in isolated pools and references them via entity IDs. The result was a modular, reusable system that mirrored ECS-style separation of data.

​

Resources were requested using string IDs, such as "PlayerIdle" or "PlatformTexture", which were defined in the object's JSON configuration. The deserializer would pass these IDs to the relevant component, typically the sprite renderer, which would then query the resource manager. If the asset was already loaded in memory, the manager would return a pointer to the existing resource; otherwise, it would load the asse from disk and store it for future use.

​

The following function shows how the manager retrieves or loads a sprite resource by its ID:

This system did not use reference counting or unloading; once a resource was loaded, it persisted for the remainder of the game session. Although this meant that there may be resources in memory that were no longer needed the scope of the game was small enough that the impact was negligible. In addition this tradeoff kept the manager simple and fast, while still cutting down memory usage by 70%. It also made game object construction far more efficient, especially when large groups of objects, like platforms or collectibles were created in bult.

​

Here's and example of how a component might query the manager for its assigned sprite and then use it:

bottom of page