What is GRIM?
GRIM is a spooky narrative game based on a Taiwanese Folklore where you play as a ghost that can possess an arbitrary number of objects to help his undead twin to safety and craft your own story along the way.
My Role - Lead Gameplay Engineer
As the Lead Gameplay Engineer, I made many Technical Design decisions regarding the implementation of systems and mechanics in C++, project organization, and work delegation.
I was also responsible for implementing most of the systems and mechanics in C++, building a foundation for the project in C++, and extending it to the Designers in Blueprints in Unreal Engine 5.
My Work on GRIM
The Headings below are drop-downs!
Possession System
Our goal for the possession system was to let the player (as the ghost) possess an arbitrary number of objects in the world. To implement this I had 3 thoughts -Â
Define what a possessable can do
This means that every possessable in the world would have a particular set of attributes. For example, how does it move, how heavy it is, what abilities can it have.Make the system modular and scalable
For a system that demands scalability, I needed to make the system as modular as possible. With the set of attributes above, I divided the functionality into enums and made the process of adding functionality very easyEvery possessable must derive from a base class
This would make the workflow of making different possessables a breeze. With the use of Enums, the designers can select the movement they want, the weight of the possessable, and add all the abilities into a set. This would make the possessable behave exactly the way they want it to.Â
Possession from a Corner Camera
Shimmy
Roll
Hop
Camera Manager and Feel
I made a custom Player Camera Manager for our Ghost Camera and Possessable Camera. This would give the player the feeling of being a ghost, guiding the twin, and getting frightened by the monsters.
Smooth Camera Transitions
Smooth camera transitions can happen with a function called RelocateCamera. This function takes in the location, speed, and the interpolation function which gives a lot of freedom while keeping it simple. A RelocateCameraComplete function is called after it finishes. Everything is handled using Event Delegates.Camera Shakes/Fades/Effects
Each of these is done using function calls to Camera Manager or Event Delegates. Flexible but simple.
Switching Camera Positions
Possession Transition
Twin Movement and Pickups
With the design decision made for the player to play as the Ghost from a corner camera perspective, we decided that a point-and-click mechanic to guide the twin would make a lot of sense. The player, when playing as the ghost
Can guide the Twin
This was done using a simple AI controller to make the twin move to a location, and an event delegate to update the position the Twin moves to.Can help the Twin interact with objects (Pickup, Use, Store)
This is done using a line trace from a distance downward and the Actor that it hits is checked and the appropriate interaction function is called.
Picking Up an Item
Using a Key on a Door
Inventory System
I made an Inventory system that is used by Twin to pick up small, but useful objects like Keys, Notes, Laterns, etc.
A Pickup class
The base class has all the information needed for a pickup from Meshes, and Interaction functions to Pickup Icons and Interaction Text. We set a gameplay tag to identify the pickupA Pickup Use class
This class is used for pickups to be used on. The gameplay tag matches the pickup's tag to trigger any functionality we like (Ex: Open a Door)
Inventory Toggle
Event-Based UI using Subsystems
Event-Based UI using Subsystems
Input Handler and Player Controller
For Input, I built an Input Config data asset and an Input handler component for the Enhanced Input System in UE5. Which
Let the designers add Input Actions, Key Maps, and Triggering conditions for the Input.
When this is done, the functionality is linked to a function named based on the name of the Input action, and the functionality is exposed to blueprints for implementation using Events.
This makes adding in Input actions without much work.
Player controllers help define different inputs for every possessable. Every Possessable having different inputs improves interactivity with the player. It gives the player the feel of playing as a Ghost controlling objects
Storage Component
The storage component can be added to any object in the scene, and it serves the functionality of being a Storable, or a Storage. While dropping or unhiding, A trace is performed around the object to make sure it is a valid place to place the object.
Storage
Storage acts as a storage space for holding one Storable. The size of the Storable it can hold depends on the Max Storage Size, varying from Very Small to Very Large.Storable
Storable objects can be stored inside a Storage. And again, it depends on its size to determine whether it can be stored or not.
Twin Holds items to be carried around
Hiding and Un Hiding
Debugging
I do a ton of debugging using Visual Studio's debugger and breakpoints, which help me understand code flow and resolve errors.
And more importantly, visual debug draws and visual logging, for things like object placement and collision handling.
Below are a few examples
Box Traces while placing down a Vase
Debugging Shimmy Movement for Cabinet with Pivot Points for movement and Box traces for collision check