Escape From Briar Mansion
Escape From Briar Mansion is a first person puzzle game written in a Custom C++ Engine using OpenGL.
The gameplay is set on a grid and turned based - Inspired by games like Legend of Grimrock and Deadly Rooms of Death.
The player explores a deadly mansion, avoiding traps and monsters - Collecting keys to unlock their escape.
Throughout the project I managed the workload of programming the engine, the gameplay mechanics and the tools used to make the game whilst also participating in game and audio design.
I worked very closely with the artists and designers to understand their needs and requirements. It was a challenging but rewarding experience.
The game supports Keyboard + Mouse, Gamepads and the OSTENT USB Dance Mat for a laugh.
Challenges
Deterministic AI
From the start, I wanted the game to be deterministic.
In theory, levels could be unit tested. If I changed something in the logic that unintentionally changed how levels behaved, then I could get feedback on that. (Though, I never had the time to set this up!)
Part of this was having an enemy AI that was both deterministic and readable by the player. This was a puzzle game after all - The Player should be able to observe the game state and make decisions based on what they know will happen.
If an enemy is diagonally adjacent to you on the grid, which tile should they step on to to get closer to you?
Well, if they have a facing direction - then we could say that they prefer to move forward over turning.
What if the path finder is facing away from you?
Well, we just don't allow that to happen. :)
Chasers in our game use a simple Dijkstra path find. They find the shortest path to the player, respecting tiles being occupied by various objects, and various edges being blocked by closed doors or similar.
When considering a Tile, an edge in front of them has a cost of 1, an edge to their side has a cost of 2, and an edge behind them has a cost of 3.
What happens if two Chasers want to move on to the same tile?
They bonk heads!
Every turn, Chasers path-find and nominate a Tile they want - then, all wants are checked for duplicates. If it's found that two chasers want the same tile, then they are instead moved in to a bonk state. This looks a bit funny, but the priority was always determinism and we didn't want to lean too hard in to common solutions like an RPG initiative order.
This results in it being possible for the chasers to get gridlocked - but honestly, the player feels a bit like they've won or outsmarted the game when this happens, so it's fine for our purposes!
Pathfinding with standard Dijkstra and modified "prefer walking forward" weights.
Chasers bonking.
First Person Perspective + Grid Based Puzzles
Traditionally, grid-based puzzle games are played from a top-down perspective (Sokoban etc). This makes it easy to communicate information to the player, however it limits us on the art side of things, and there was a top down grid game last year at AIE!
I really wanted to do something similar to Legend of Grimrock - Where the game is played from a first person perspective, but still aligned to the grid.
Player Controller Version 1 - Grimrock Clone!
The Players view is locked to the cardinal directions.
They press WASD or move in a cardinal direction, and they press Q or E to turn 90 degrees in either direction.
The Players position is standing in the 'back' area of the tile.
If they are facing North, then their position on the tile is slightly South from the centre.
The Player uses the mouse cursor to click on objects in the scene to interact with them.
This is exactly how Grimrock works.
Problems:
The Art doesn't look as nice if it's only ever viewed from harsh directions.
The Player struggles to review their surroundings if they can only view it in increments of 90 degree angles.
Player Controller Version 2 - Free Look!
We introduced a Free Look option! The reluctance to unlock the view from cardinal directions stemmed from the concern that The Player may get confused about which direction they were facing, and how their movements might behave.
To combat this, we made the free look elastic, and a state of it's own.
To Free Look. The Player holds down rightclick and moves the mouse. Whilst doing this, they cannot move or perform any interactions.
When The Player releases right-click, the camera rubber bands back to be aligned with their facing direction.
This worked well, but..
Problems:
Immediately in testing, Players wanted to be able to move and interact whilst in this mode.
Player Controller Version 3 - Global Free Look With Auto-Reorient!
I removed the state from Free Look, and made it a behavior of all existing player states.
I also added a threshold where if The Players look delta from their facing direction breaches a certain threshold, then the player controller will automatically perform a TurnLeft or TurnRight action to keep their actual orientation fairly close to their Free Look orientation.
Additionally, we had locked in that for interactables, there would only ever be one interactable object in any given facing direction - So we could use an interact button instead of having to actually click on the object in the game - and now we can hide the mouse cursor!
but..
Problems:
Players find the auto-reorient weird - Because the Free Look feels so much like playing a first person shooter - the little hitch from the auto-reorient just weirds people out - they know they are grid aligned, but they just don't care about this little movement. - But, because the player stands not on the centre of the tile, but backwards a little bit, when we re-orient them we cant just snap this position immediately. So...
Player Controller Version 4 - Global Free Look With Instant Auto-Reorient and Tile Centering.
I moved the player to the centre of the tile, and made the auto-reorient instant.
With the player now standing in the centre of the tile, if their Free Look delta crosses 45 degrees, I update their cardinal direction and subtract 90 degrees from their 'look offset'. This happens silently - the player doesn't notice - but they are now facing in a new direction. Pressing 'Forward' would now move them in a different cardinal direction to what they were originally facing.
Releasing the Free Look button will result in their view snapping to the new cardinal direction, rather than the old one.
If The Player presses Q or E to turn, then that's still something that's animated.
We also enabled an option in the menu to 'Always Freelook' - The Player does not need to hold right click. We found that with the tiling on the ground and how levels are laid out - the grid-based nature of the game communicated itself and players got used to it - they did not need hand holding from the Player Controllers camera system.
First version. Very rigid.
Free Look introduced. But what is forward?
Auto-Reorient - A little disorienting
What I Would Do Differently Next Time
Move from a 'Prototyping' to 'Production' programming style earlier.
One of my strengths as a programmer I feel, is my ability to bash out working systems very quickly. This was very useful during this production to get a prototype up and running quickly, whilst building the engine and the tooling around it.
However, once we had locked down some 'will haves' and 'wont haves' - I could have transitioned to a much tidier code-base much earlier.
Most entities in the game have a position, a facing direction, a pointer to their object in the scene graph that visually represents them, and various other shared behaviors - but I never refactored to set up any kind of inheritance structure. This resulted in some pretty messy code and a lot of boilerplate configuration when adding a new entity.
If we were to go back and do a sequel, I would absolutely clean this up.
Set up Unit Testing with Jenkins.
Because the game is deterministic, it was possible to describe solutions to puzzles as a simple series of inputs.
Sometimes, changes to the codebase would introduce issues or changes in game-play logic.
I would have loved to have paired levels with solution data files, and then run tests against the campaign levels, and a series of test levels to catch if/when behaviour has changed so that we wouldn't find it for the first time during a playtest or showcase!
Not be afraid!
I put off some tasks for a long time because my understanding of them was misguided. I often assumed they were going to be uninteresting, difficult or time-consuming and found myself not wanting to dive in to something that perhaps was higher priority!
Though, on reflection - Everything that went in to the project ended up being interesting, every challenging tasks was achievable and I felt proud at the end. Of course, everything consumes time and we get better at predicting how long things will take.
I would often plan my tasks out, interleaving things I considered to be fun in-between things I hadn't yet realised were also going to be fun.
At the end of this project, if there's one thing I take away from it - It's that I really enjoy programming. Whether it's implementing gameplay, hunting down a memory leak, or setting up some automation - I finish the day with my bucket full rather than drained.
Going in to my next project, I will take every task as an exciting new challenge.