Infrastructure
Open Source Libraries
One of the first challenges I encountered while developing MODERN MEDICINE was deciding how to organize the code. After researching possible frameworks, I decided on using Knit in order to simplify communication across the client-server boundary and to take advantage of its built in structure with services and controllers. I also use Rojo as part of my workflow in order to program in VSCode instead of the Roblox editor and to make version control with github easier.
I also took advantage of the RbxUtil library to further streamline development and prevent myself from reinventing the wheel. Out of the entire library, I found myself using Components to streamline and organize tagged objects as well as Troves to clean any connections or other items after they are no longer needed. The RbxUtil libraary also provides the Signal data structure, which is a core part of Knit, to simplify handling RemoteEvents and RemoteFunctions.
ProfileService is also used for the backend (see the backend section).

Regions
The region system is a core part of the code base. It continuously calculates each player’s position within the node graph. If the player moves into a new node, an event is fired. It also keeps track of various statistics regarding each region, such as the number of players currently in the region. Some of the areas the system is used includes:
- Determining if client-side monster effects can play (e.g. Larry’s footsteps causing screen shake only in the hallways)
- Determining player position in the hallways for use in monster chase logic
- Communicating the player’s level location to the client (i.e. which hallway biome or room is the player in)
- Dialogue cues when a player enters a room
- Locking players into a room when they enter it
- Determining which sounds a player can hear
Linear Interpolation
The Roblox API provides an excellent base of tools, especially for lerping objects using their Tween library. However, this library is not without its limitations. Throughout the game, players will loot objects for various quests or to collect/purchase items while moving. With the Roblox Tween library, it is not possible to interpolate an object to a moving target. This means if a player picks up an object while running, the object will move to where the player was at the time the object was picked up, not where the player is when the interpolation finishes. To do this, I wrote a custom module accounts for the movement of the player after an object is picked up. This module also contains networking code which allows every player to perform this interpolation locally on the client, rather than on the server. This makes the interpolation appear smooth because it runs at the client’s frame rate rather than the reduced tick rate of the server.

There are various other linear interpolation modules implemented into the game that would not be possible using the Tween library.
- Using distance instead of time to calculate the transparency of biome walls as Larry approaches them, preventing the monster from appearing to phase through the wall.
- Using distance to determine the frequency at which the biome doors themselves pulse (or if they should not pulse) when Larry approaches
Other Miscellaneous Tooling
Level Building
The core system used when populating the level with assets is the Ghost system. This system went through several interations in order to balance the technical complexity and ability with ease of use for the level designer. For a brief description, a ghost is a primitive object tagged as such that has a path attribute. The path attribute links to a folder which contains any number of potential assets to spawn at the ghost object’s location. For ease of development there is no strict format enforcement of the file structure. Folders can be nested anywhere, and when a floder is linked only model objects will be considered for spawning (allowing folders and models to exist within the same directory).

This system allows for individual asset spawning probability to be customized and more complicated “ghost structures” to be formed, such as generating the hallways or forming loot groups within the world. Ghosts can also be nested inside assets spawned by ghosts, allowing for extreme customization and differences between levels.
Individual rooms can also randomly select certain themes to randomly choose elements from while using the same ghost layout. For example, the level designer can create one office layout but have different groups of ghosts that represent different theming. This way, if the office is randomly chosen to be safari themed, the randomly selected assets will only be from the safari bucket.
Room Construction
Rooms don’t have to be square. They can be any shape the level designer can imagine. However, to simplify room placement in the node graph, rooms are requiered to be built on a square surface whose width and height are divisible by the default node size (so it can be divided into nodes). To prevent a bunch of dead space in the node graph, the level designer can place “air tiles” on unused portions of the room’s square surface. This signals to the node graph at runtime that these nodes are unoccupied and may be populated with other specialty nodes such as hallways.
The level designer can also place multiple “door tiles” on the build. These tiles do not necessarily need to be on the perimiter of the square surface as long as there is a way for a hallway to reach it (using air tiles). These door tiles will spawn a door to the room and guarantee that a hallway spawns on the node bordering it.

Hallway Construction
Hallways are constructed by populating a ghost node with assets from the relevant biome. All hallways need walls, doorways, biome doorways (double doors), ceilings, and floors. Additionaly, they may have ceiling props, wall props, and straightaways. Each category can have multiple possible assets to spawn and each asset can optionally have its own unique spawning probability. At runtime, the node graph is used to calculate which sides of the node walls, doorways, etc. need to spawn in.


Generic Doors
There are a lot of rooms in MODERN MEDICINE. Beacuse doors require some scripting overhead, I created this system for the level designer to be able to easily and quickly create new door designs and sizes without any programming overhead. These generic door objects will open when a player stands close to them, and close when they have left the vicinity. All the level designer has to do to create a new door is group assets together and name the “door part” of the door as “Door”. At runtime, the program will calculate the door hinge and hitboxes and begin listening for player interaction.

Sliding Doors
Similar to generic doors, sliding doors are also streamlined for the level designer. They contain a “Door” object which is the primary object in the door, as well as two distinct sets of parts: “MovingParts” and “StaticParts”. At runtime, the door is primed and a prompt is proximity prompt is placed into it. When the prompt is activated, the sliding door slides its body length to the right in order to open.

Sound Effect Parts
Quality atmospheric sound design was one of my key ambitions for this project. To assist with this goal, I created sound effect parts and their supporting infrastructure. Sound effect parts are components which will play a specific sound, and also contain metadata about how the sound should play. Some of this metadata include:
- Sound Location (i.e. can be thound be heard in the hallways or just a specific room)
- Playback range
- Sound table information (are we pulling a random sound out of a group? e.g. one of several thunder sounds) or sound name
- Playback information (should we loop the sound, should we randomly select a sound from a sound table and loop it, should we play every sound in a sound table back to back, etc…)
To minimize the amount of objects that needed to be transferred between world files during development, all sound data is serialized in a master sound file.
Light Flickering
Light flickering is implemented similarly to how it was in Quake (and still implemented in modern games like Half Life: Alyx). Light patterns are stored as a string of characters ranging from a-z. When enabled, the program reads one character individually and adjusts the light’s brightness to the brightness represented by that character. Then after a small delay, it does the same with the next character. When it runs out of characters, it starts over at the beginning. An additional modification I made to this idea is adding upper case characters to the character strings that represent certain sounds, e.g. a “light popping” sound.
