top of page
Grave Secrets

Platform
PC, iOS, Android


Engine
Unity

Duration
May 2021 - November 2021

Team Size

8

 

Overview

Play as a psychic detective who must track down a stolen item in this 2.5D puzzle game. Infiltrate a graveyard, avoid being caught by gravekeepers, and switch between the real world and the spirit world to reach and speak to ghosts who will guide your way.

Accomplishments

  • Wrote detailed documentation for puzzle mechanics and implemented alongside programmers.

  • Implemented tools to more easily and quickly design grid-based levels in editor.

  • Created a save/load system and nonlinear level selection to facilitate smoother progression.

  • Designed 19 levels on paper and then built them in Unity.

Breakdown

Initial Mechanics

The initial concept for the game was that the player would need to reach a target location without touching any enemies, and must plan all of their moves in advance. I collaborated with programmers to implement this in-engine, like so:

GraveSecretsPortfolioOld.gif

The right side of the screen contains all of the actions that the player can perform. The player selects those actions to place them into the sequence displayed at the bottom of the screen, and when the player presses the start button, that sequence of actions plays out.

The game is split into a Planning Phase and Action Phase. In the Planning Phase the player chooses the sequence of actions they'll perform once they enter the Action Phase.

The player can:

  • Move in four directions.

  • Wait for a turn.

  • Switch worlds (more on this in a second).

​

The player must reach the pink ghost without touching any enemies, and they have a limited number of actions to do so (different levels provide a different number of maximum actions).

​

Levels are comprised of two parallel worlds: the real world and the spirit world. The player can preview both of them in the Planning Phase and must switch between them like so in the Action Phase:

GraveSecretsPortfolioSwitchWorldsFail.gif

A level that requires switching into the spirit world. The player fails here.

The player must retry the puzzle if they fail to reach the ghost by the end of their sequence of chosen actions or if they touch an enemy. The action sequence persists across attempts, so that the player can try again without having to re-input the entire sequence.

GraveSecretsPortfolioSwitchWorldsSucceed.gif

This time the player succeeds.

Playtest Feedback

The "Plan Ahead" gameplay was intended to encourage thoughtful play, but I found in playtesting that it encouraged the opposite. Players were not thinking ahead to figure out the right series of moves that would beat the level. Instead, it was much easier to throw together a sequence of moves, see what happens, and make minor adjustments, repeating the cycle until the level is finished.

Several playtesters also slowly adopted an interesting strategy: they worked backwards, filling out the sequence in reverse order.

Another issue was that enemies travelled designer-specified paths, and the player could view an enemy's path in Planning Mode by hovering their cursor over that enemy, but calculating where an enemy will be on a particular turn was difficult enough that players did not bother; they just resorted to trial and error.

Ultimately, the consensus from playtesting was that the game was currently very tedious and did not provide an interesting puzzle-solving experience.

Problem Analysis

Before jumping to solutions, I try to identify and articulate the core problems that are preventing the game from being a satisfying puzzler. Here is an except from some internal documentation I wrote on the subject: 

The hard part of a puzzle should be figuring out what to do, not executing the solution. Each puzzle should have a core idea behind it that the player must come to understand, and once the player reaches this moment of epiphany the puzzle has effectively been solved. All of this happens inside the player’s mind. Executing the solution in the game should be the easy part, because once the player has figured out the idea behind the puzzle there is no more mystery present, and the mystery of what to do is where the fun comes from. Figuring out how exactly to execute the solution is not nearly as interesting, which is why executing the solution should be easy and quick; that way the player can move onto the next puzzle and experience more mystery.

I go on to give an example from this level:

GraveSecretsPortfolioOld.gif

The idea behind this level is simple, but it does have a core idea: wait for the guard to move to the left, so that you can get past him. This is the first level that forces the player to wait, so “I need to wait” is the core realization for this puzzle. It sounds easy and it should be, because this is only the fourth level of the game.

 

The problem is that in the current game, figuring out “I have to wait” is the easy part of the puzzle by a very wide margin. Since the player must plan their moves out in advance, the difficult part of this puzzle is figuring out precisely how many turns the player must wait until they are able to get past the guard. “How many turns do I have to wait?” is not an interesting question; it feels tedious to arrive at the correct conclusion because this is the execution phase of the puzzle. The player already knows what they have to do to solve it; they’re now just working out the minutiae.


The end result in the above example is that level 1-4 ends up being more difficult than it should be for such an early level in the game, and it’s the wrong type of difficulty for a puzzle game. The difficulty should be in concepts, not execution.  This problem would only worsen when adding more mechanics and more complex puzzles to the game, as all of the current levels should be fairly easy given that they’re only worlds 1 and 2, but they’re quite difficult due to this core problem.

Solutions

The meat of the solution was to drop the "plan ahead" gameplay and simply let the player control the character directly. This was a massive change, but a necessary one. The "plan ahead" gameplay was completely at odds with the inclusion of enemies that move around the level independently; it was just requiring players to keep track of too many things in their mind at once, which is why players disengaged and resorted to trial and error.

​

So I implemented that change like so:

GraveSecretsPortfolioNewInput.gif

The player no longer has to plan their moves in advance. They control their character directly and can switch worlds by pressing Tab or wait a turn by pressing Spacebar. They can also still preview the other world by clicking the button on the bottom-left.

Another important change is that enemies no longer travel in a scripted path, since that was another thing that encouraged trial and error (having to stop and hover the cursor over enemies to see where they would move was not good). Instead, I made it so that the enemies follow consistent and predictable rules inspired by Hitman GO and similar games. I'll get into those rules in the next section.

New Mechanics and Puzzles

Another critical benefit of these changes is that we've simplified the execution aspect of each puzzle, which allows us to focus on the concepts behind each puzzle instead. This is key to making satisfying puzzles, but our current mechanics were so simple that they didn't present much opportunity for interesting puzzle design, so I took the opportunity to add a few more mechanics.

When the player reaches the goal in each level, they initiate a conversation with the friendly pink ghost. This initially froze the game, as you've effectively completed the level. However, I experimented with making the game continue even during the dialogue, with one turn passing each time the player advances to the next line.

GraveSecretsPortfolioDialogueDanger.gif

The player is still in danger even when talking to the ghost at the end of the level. In this case, the solution is to move downward and wait for the enemy to pass before talking to the ghost, ensuring you have enough time to safely finish the conversation.

This mechanic did have some downside: dialogue previously served as a reward for completing each level, giving the player a chance to relax. I replaced this with additional tension, but it was ultimately worth the trade-off, as it introduced too many level design opportunities to pass up and we could now make more interesting puzzles for which the act of solving is reward enough. This mechanic is not relevant to most of the levels in any case, so the player can relax if it's clear that they're standing on a safe tile while talking to the ghost.

More mechanics also arose out of designing the new enemy rulesets. These basic enemies just move in a straight line until they hit a wall and then reverse, and they repeat that indefinitely. But some questions arise: what happens when two enemies overlap each other? I saw this as an opportunity to introduce a new mechanic: allow enemies to knock each other out like so:

GraveSecretsPortfolioEnemyDeath.gif

If enemies collide they knock each other out the same way that they KO the player. This removes them from the level, allowing the player to reach the exit.

Even with only these 2 new mechanics, I can start thinking about how to combine them to create interesting puzzle concepts. Here's an example:

GraveSecretsPortfolioSoClose.gif

In this level, titled "So Close," the ghost that the player needs to talk to is right in front of them but if they try to initiate conversation right away they'll be KO'd by the enemies during the dialogue. The player needs to retreat and kill some time until the enemies KO each other, and then it's safe to talk to the ghost.

The above puzzle has a clear eureka moment, which is essential to creating satisfying puzzles. I can design more of these eureka moments the more mechanics we have that interact with each other in meaningful ways.

So I didn't stop there; I added another mechanic that interacts with the previous additions:

GraveSecretsPortfolioEnemyGhosts.gif

When enemies die in the real world, they become ghost enemies in the spirit world.

This again allowed me to create more interesting puzzles with unique concepts behind them, like this one:

GraveSecretsPortfolioStuck.gif

If the player is too slow, ghosts will spawn that prevent the player from completing the level. They must reach the exit before the ghosts spawn by taking the less obvious leftward path, switching into the spirit world to get past the gate.

Nonlinear Level Selection

With the above mechanics I was able to create 19 levels each containing some unique idea, but there are some problems with the naïve approach of requiring the player to complete them all in a linear order. Getting stuck on a puzzle becomes an impenetrable roadblock if you must complete that puzzle to progress, and this can easily lead to frustration that causes players to quit the game. A common approach to avoid this is to present levels with some degree of nonlinearity (see Baba is You or Stephen's Sausage Roll for examples), such that if the player gets stuck on a puzzle they can generally try a different puzzle and come back later. This is the approach I went with for Grave Secrets, and so I created this overworld level selection feature:

GraveSecretsPortfolioOverworld.gif

The player selects a level from this overworld screen. Gold levels have already been completed, purple levels haven't been completed but are unlocked, and grey levels are locked.

The levels are divided into worlds, each introducing a new mechanic. World 2 introduces the world switching mechanic. I outlined several potential additional mechanics that we could introduce to create more levels for more worlds, such as:

  • Boxes that characters can push

  • Different enemies with different rules

  • Keys that characters can pick up which unlock gates

  • Pressure plates that open trapdoors

  • Spike traps that emerge from a ground tile one turn after a character steps on that tile

We stopped working on the project before tackling any of those features, but each would have introduced interesting interactions with the existing mechanics to serve as the basis for swathes of new puzzles.

On the technical side, I collaborated with one of our programmers to implement the level progression system; he implemented the underlying functionality of saving the status of each level (locked, unlocked, or completed) to a JSON file and I utilized that functionality when implementing the higher-level progression logic.

​

I leveraged Unity's ScriptableObject class to create data objects for each level with the following data:

OberakPortfolioGraveSecretsLevelDataObjectDefinition.PNG

Level data object definition. Note that the CreateAssetMenu attribute allows designers to easily create new LevelObjects in the editor by right clicking anywhere in the project files and selecting Create->Level->New Level Object.

OberakPortfolioGraveSecretsLevelDataObjects.PNG

All of the level objects in the project visible on the left, with the details of the selected level 2-9 visible on the right. Note that this level requires 7 other levels to be completed before it is unlocked.

Each level object references the level objects of the other levels that need to be completed before that level is unlocked, as seen above. The Storage Key is the key that is saved into the JSON file alongside the status of the level (locked, unlocked, or completed). Each level object is also associated with a button in the overworld:

LevelButton.PNG

The button for level 1-2. Note the reference to the "Chased" level object on the right.

Game progression works pretty simply:

  • Whenever the overworld level is loaded, a script gets references to all of the level buttons in the scene.

  • The script gets the associated level objects for all of those buttons.

  • The script then queries the JSON data for the level's status given its storage key.

    • If the key is not present in the JSON data then the script saves that key alongside the Locked status.​

  • For each Locked level, iterate through its RequiredLevels array and query the JSON data with the storage keys of each of those levels. If all of the levels have status Completed, then set the status of this level to Unlocked.

    • The first level in the game has an empty RequiredLevels array, which makes it unlock immediately.​

  • Whenever the player completes a level, set its status to Completed.

Iterating through every Locked level object and all of its RequiredLevels every time the overworld loads is certainly not the most performant approach in theory, but in practice it had no perceptible impact.

Grid-based Level Design Workflow

Level state is represented by a two-dimensional array of type Tile, where Tile is an enumeration representing the different objects that can occupy a tile:

  • Empty

  • Player

  • Target

  • Obstacle

  • Enemy

The puzzle mechanics operate on this 2-dimensional array. For instance, when the player attempts to move I first check the contents of the the destination tile by indexing into the level data array and do not allow moving if that tile contains an Obstacle.

​

There's now a big question: how do we populate this underlying level data when we design new levels?

​

Our programmers initially just defined this data in C# and then spawned objects into the scene at runtime so that the visual representation of the level matched the underlying data representation. But this method had extreme limitations:

  • Level designers had to edit the C# code to make new levels or modify existing levels.

  • The visuals of all levels are created procedurally with simple blocks, so we cannot easily place art assets and light sources into the level and have them align with the grid.

​

I collaborated with one of our programmers to implement a new system that would make it easy to design and art levels. Rather than define the level data in code, the data is defined in the editor like so:

GraveSecretsPortfolioTaggedObjects2.PNG

Dots are drawn in the viewport at the center of each tile. The different colored cubes are associated with tagged objects that level designers place in the scene.

A script defines the location of the tiles of the grid in world-space, and draws small spheres at the center of each tile. Level designers can then turn on grid-snapping to place tagged objects on those tiles, and a script searches the level for tagged objects, converts their positions from world-space into their indices in the underlying 2D level data array, and then sets that element in the array depending on which tag the object has. 

​

I made prefabs for 4 tagged objects that level designers can use to define level data:

  • Obstacles are represented with a white cube gizmo.

  • The target tile is represented with a green cube gizmo.

  • The player's spawn is represented with a blue cube gizmo.

  • Enemy spawns are represented with a red cube gizmo.

These gizmos are only displayed in the editor, not in the game itself. Level designers can rotate the character spawns to change which direction the characters are facing when they spawn in.

​

This was a massive workflow improvement. Beyond making it easy to populate level data, it also made it easy to art levels as we separated the visual representation of the level from the underlying data. We could easily set-dress levels without worrying that props might register as obstacles and block the player, for instance.

​

The workflow could be improved further with a bespoke 2D editor tool. It would be even more convenient to be able to just click on a 2D display of tiles and specify obstacles and object spawns without having to drag objects around in the 3D viewport, but ultimately this workflow was plenty efficient for our purposes. I also made a prefab "LevelSkeleton" containing all of the objects needed for the level to function, so that level designers could quickly create new levels with this empty skeleton as a base, simply adding tagged object prefabs to the grid as desired.

LevelSkeleton.PNG

The "LevelSkeleton" prefab that served as a blank slate starting point for level design, containing all of the essential objects that levels need to function.

As you can see in the above image, the real world and spirit world are two separate locations in the world. This was a far simpler approach than having them inhabit the same space and toggling which assets are visible when the player switches worlds. Instead, two cameras are constantly rendering each world, and I just switch which rendered image is displayed to the screen depending on which world the player is in, crossfading between the images on transition.

bottom of page