At Her Expense

Platform(s): Windows
Engine and tools: Unreal Engine 5
Role: Game Designer and Programmer, Technical Artist
Time spent on project: 6+ months
Status: Released

Interaction with objects Data-driven dialogue Branching quest system
Download here

Relevant contributions

Interaction system

The gameplay revolves around exploring and interacting with the environment. I programmed a classic interaction system based on raycasting: if the line traced from the player's camera hits an interactable object, it gets highlighted and the proper widget is shown to the player.
The specific objects implement the interaction methods, declared in a common abstract superclass.

The interactions are quite diverse: they range from simple physics-based actions, such as pushing or throwing objects, to triggering distinct subsytems. Among the latter, the most interesting ones are the real-time painting system and the simulated computer interface.

The painting system has been implemented drawing repeatedly the spray mark texture on a Render Target. The Render Target is used as a dynamic texture parameter in the wall's material. The position where to draw the spray mark texture gets computed using line tracing on the wall and storing the UV of the trace hit. This flexible system allows obtaining different painting effects solely changing the mark texture; for instance in game a sponge can be used to erase what you have drawn and it works by "painting" with a white square texture. Therefore this system could be easily extended to use any colors or brush effect.

For the computer interface I designed a class hierarchy rooted in a BlankFile widget class, where I implemented behaviours common to all types of files. The content of the file is displayed using a Window widget class, that reacts to the type of file opened by the user. For the sake of simplicity I avoided defining folders, since that would have required drawing overlapping windows and managing input on multiple layers. I decided to keep it simple, making the single window on screen blocking. The initial arrengement of the files and their content are defined in a DataTable, in line with the data-driven approach used extensively throughout the game.

Quest system

Throughout the game the tasks to complete are displayed on the contract on screen, that functions as a diegetic UI. My first programming task was to design the quest system, which had to support notably:

  • Tasks consisting of repeating any type of action n times
  • Groups of tasks that appear simultaneously on the contract
  • Dynamic task sequences that change based on the player's behavior
Based on these requirements, I designed the task system as a tree data structure, since it is impossible to return to previously completed tasks. Specifically I envisioned a tree similar to a Finite State Machine, composed of TaskNodeslinked by guarded transition arcs.
I stored all the data describing the task system in two DataTables, one for the tasks and the other for the transitions, where the tasks are grouped in TaskNodes. Each task is characterized by an ObjectiveID, whose purpose I will describe later, and by the number of repetitions required. Each transition is characterized by its source and target TaskNodes, the presence and type of guard (time-based, based on the Evilness value, or based on a GameplayTag), and the values associated with the guard's condition.

When the contract is spawned, a ContractTreeComponent scans the DataTables and builds the corresponding tree, rooted at the initial TaskNode. The flexibility of this system allowed us to modify the task sequence throughout the game's development without requiring changes to the underlying code.

To support task completion, I developed a scalable system capable of handling every type of objective. I assigned an ObjectiveID to each task and bound every Task object to an event dispatcher in the ContractTreeComponent. Whenever the player performs a relevant action, the corresponding interactable object calls this dispatcher, passing the ObjectiveID associated with that action.

Each Task object compares the received ObjectiveID with its own and, if they match, processes the "signal". Every Task object keeps track of how many matching "signals" it has received, and once the required number is reached, the task is marked as complete.

This system was also effortlessly extended to support reverting task progress by decrementing the stored "signals" count. This feature is used by the main NPC to interfere with the player's progress by performing the opposite of what the contract requires.

The architecture could be easily expanded to support dependencies between tasks or objectives composed of multiple sequential stages, making it suitable for implementing the classic quest systems commonly found in role-playing games.

Dialogue system

The game begins and ends with sections inspired by visual novels, where the player can "hear" the higher beings involved speaking. Although the player cannot respond to their interlocutor, the system I developed still includes a dynamic component: in the final section, the dialogue varies based on how the player has behaved throughout the game.
The dialogue is divided into sections, each identified by a tag and linked to the tag of the following section(s). The dynamic aspect comes from associating each section with a set of conditions. This allows multiple sections to share the same tag, with the appropriate one being selected at runtime based on the game state. These conditions are based on two variables: the Evilness value and the Progress value. The former is calculated by summing the evilness of the player's actions, while the latter is determined by the number of tasks the player has completed. This system enables different dialogue branches and endings depending on the player's behavior. All these informations are stored in a DataTable, in line with the data-driven approach used extensively throughout the game. The data structure for the dialogue is much simpler than the one used for the tasks, as it is based on static values. Once the player reaches the final section, the dialogue can no longer change, because the relevant values have already been determined. As a result, the dialogue structure is not a tree but rather a set of parallel sequences, with the appropriate one being selected when the player reaches the end of the game.