Dream Walker

TEAM
Deliverone FutureGames Game Project 1
Category

Team Projects

Year

2021

Project Duration

4 weeks

Context

FutureGames Game Project 2

Tools

Unity, C#

My Responsibilities

Pickup Interaction System, Input System, Settings Manager, Sfx Integration

Team Members

Artists
Alishia Nossborn
Gustav Nilsson
Simon Eliasson

Programmers
Oliver Lebert
Justus Hörberg
Sebastian Åkerlund

Designers
Simon Olivecrona
Jesper Westlund
Samuel Einheri
Hanna Högdin

Music
Jesper Westlund
Adam Wiger

TL;DR

Dream Walker is the project I’ve been most passionate about so far, the team work worked incredibly well and I also got a good excuse to write an advanced system (somewhere between a gameplay system and a tool) that required a custom inspector. It saved tons of time for the designers during the project.

Project Breakdown

Introduction

DreamWalker takes place within a nightmare. The player gets to explore within this dreamworld. Your aim as a DreamWalker is to prevent a girl from being trapped in her nightmare. Traverse different areas of her nightmare, solve puzzles, and manipulate your environment in this third-person point and click adventure. Do whatever it takes to fulfill your duty.

This was my second game project at FutureGames which I made along with 9 other super talented teammates. The project was four weeks long and it was super fun to make. Since I had worked on some tools before this project, I was familiar with custom inspectors which proved to be super useful under the project, more on that below.

Compared to my first FutureGames project I am actually impressed with myself over the amount I have learned in just a few months. My code is so much nicer and easier to read and I was able to make many advanced systems that are modular and easily scalable should, for instance, a new puzzle want to be added.

The Code

The code in this project is a lot more complicated than in my last project. The interaction system is easily the biggest part of the game since the game is so focused on puzzles. I made the foundations of it and most interactables that are used in the puzzles. My programming colleague Justus worked primarily on the movement/camera and Sebastian was a bit of a generalist. Notably, Sebastian made the outline shader when you look at interactable objects.

Going back to my main responsibility, the interaction system, let’s take a look at the class diagram, showing the relevant classes to the system in color and their immediate dependencies in gray:

DreamWalker Class Diagram

The Interaction System

The interaction system starts with interactables which are classes that derive from a main abstract Interactable class. They recieve either/both key down and key up events from the bound interact key or mouse button. Based on that input it will perform any logic specified in that derived class. It can also, in some cases trigger an output which is just a delegate that any external system(s) can subscribe to, to perform additional custom logic when that delegate gets called.

There are a fair few implementations of Interactables. Here is a list of some of them:

  • InteractableToggle, used most often in DreamWalker, toggles on/off at key down.
  • InteractableMovable, used in the statue and pyramid puzzles. Specifies a starting state of an object, an array of possible states, and an index of a correct state. If the first state in the array is occupied by another movable, the current movable will try to move to the next, etc. If the movable is in the correct state it will give an output.
  • InteractableMoveToPlatform, the platforms that when clicked, the player will move to them. There are options to specify the arc in which the player travels (using bezier curves), as well as the speed using an animation curve.
  • InteractableTrigger, an interactable that cannot be clicked but will only trigger the output when the player walks into its trigger collider.
  • MovingPlatforms, the moving platforms just before the pyramid puzzle (made by Sebastian).

InteractableMoveToPlatform

InteractableMovable as part of the statue puzzle

Some of these interactables make up the framework for the inputs in the puzzles, while others are more standalone (such as the MoveToPlatform). Now there’s only one key thing missing, what these inputs trigger. For puzzles, the PuzzleHandler lays the foundations of that. It’s quite simple, it takes an array of Interactables as input, and when they are all outputting true, the output will be triggered.

The Interactable Event System

When we were making the project the designers had defined lots of puzzles already with many different needs. This made the outputs of the interactables pretty hard to make correctly because it has to do a lot of things. For example, when completing the statue puzzle, a cutscene has to be triggered, a sound must play, the platforms you used to traverse to the island have to be disabled, the bridge to lead to the next part of the game must become interactable, etc. Other puzzles have completely other outputs that must be triggered. I realized that there were two ways to solve this, either I make a script for every puzzle that will do the things mentioned, which would get very messy very quickly. Or I make a generic system where all events can be customized completely in the inspector. This would mean that I wouldn’t have to do any additional coding, I can just give all the power to the designers to customize exactly what will happen when either a puzzle is completed or an interactable is triggered. I of course made the latter which I named the InteractableEventSystem.

This is how the inspector looks:
InteractableEventSystem

You attach either a puzzle handler, an interactable, or a UI button to the field, the event system will subscribe to a delegate in the relevant object that will be triggered when the interactable or puzzle handler’s output switches to true. The event system can also be customized to trigger as soon as it is enabled. The events will be executed from top to down (as configured in the inspector), you can add a delay to events and some events will wait until they have finished executing before running the next event. For example, when showing subtitles (makes it easy to make a subtitle sequence without having to think about delay etc) and waiting for the player to close the diary (in the beginning), etc.

Because of the complexity of the script and the number of things you can do with it, it had to have a custom inspector to make sure everything has the correct layout and that only relevant controls (based on the event type) are shown at one time. Luckily, I had worked a lot on a tool just before the project so I had even made an editor library that I could just import into the project. This enabled me to get the event system up and running in just a single day because I was still in the tool-making mindset.

[code lang=”csharp”] public static void Indent(int numberOfIndents = 1)
{
for (int i = 0; i < numberOfIndents; i++)
EditorGUILayout.LabelField("", GUILayout.Width(MarginInsideFoldout));
}

public class LabelWidth : IDisposable
{
private float _oldLabelWidth;

public LabelWidth(float width)
{
_oldLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = width;
}

public void Dispose()
{
EditorGUIUtility.labelWidth = _oldLabelWidth;
}
}

// Helper methods like these allow you to not think about layout or writing SerializedProperty.value = Field(text, SerializedProperty.value) etc.
// Just pass in the reference to the serialized property and everything will be handled
public static void FloatField(GUIContent label, SerializedProperty property, FieldMode mode, int numberOfIndents = 0, int labelWidth = MaxLabelWidth, params GUILayoutOption[] options)
{
if (numberOfIndents > 0)
EditorGUILayout.BeginHorizontal();

Indent(numberOfIndents);

using (new LabelWidth(labelWidth))
{
if(mode == FieldMode.Instant) property.floatValue = EditorGUILayout.FloatField(label, property.floatValue, options);
else property.floatValue = EditorGUILayout.DelayedFloatField(label, property.floatValue, options);
}

if (numberOfIndents > 0)
EditorGUILayout.EndHorizontal();
}[/code]

The tool was really fun to make, and it was a joy to see how much it was used. There are over a hundred instances of the event system across all scenes in the game (including test scenes). I can just imagine how much time would’ve been lost during the production for both programmers and designers, if all of that would’ve been hardcoded.