Author: Julian

  • Revealing Our Second Game: Between Horizons!

    Revealing Our Second Game: Between Horizons!

    Hey, everybody!

    In case you are new here: We are German indie studio DigiTales Interactive, developers of critically acclaimed sci-fi noir adventure Lacuna. Today, we’d like to introduce you to our upcoming second title, Between Horizons, that’s coming to PC and consoles in 2023, once again in cooperation with our publisher Assemble Entertainment!

    What Is It?

    Between Horizons is a story-driven detective game set aboard the semi-open world of a generation ship en route to another star. You assume the role of Stella, the ship’s young Chief of Security, who is faced with multiple problems threatening to jeopardize the mission. You will have to make some tough moral choices and think hard to unravel the complex conspiracies on the ship if you want things to end well for Stella and the mission. That’s right: Just like in Lacuna, the story branches and ends based on your decisions and performance as an investigator.

    The gameplay will feel familiar to fans of our first title: direct platformer-style controls, non-repeating branching dialogs, automatic saving (no going back!), collecting clues, submitting solutions to cases. However, we made some major improvements to the formula! In the next few paragraphs, we’d like to provide insights into our thinking behind some aspects of Between Horizons. If you want more marketing speak instead (and of course add the game to your wishlist ❤️), just check out its store page!

    Refined Detective Gameplay

    At its very core, Between Horizons is a detective game. We learned a lot about investigation mechanics while working on Lacuna, and we kept around some of the things that worked well:

    • Cases are solved by filling in gaps in case sheets: you must complete sentences using names of people, places, and other clues you found.
    • You can always drive the plot forward by submitting a case, even if your solution is wrong. If that happens, you may not know until later, when the consequences manifest themselves.
    • Instead of alternating cutscenes with self-contained mini games, all the puzzles you solve are directly connected to what’s happening in the story.

    Good detective games usually give the player a great amount of freedom to come up with the solution on their own. Hence, after a lot of trials and discussions, we landed on some improvements to the formula that aim to maximize player agency:

    • You can submit the solution to a case at any point in time. You have to decide whether or not you know enough to crack it yet. This also makes it much easier to quickly replay content if you know the solution already.
    • Any evidence you find (in the entire game!) can be assigned to any case (in the entire game!).
    • Instead of being presented with a linear string of levels, you can freely move around parts of the ship to investigate. New areas unlock in a Metroidvania fashion as you progress through the story.
    • You can present characters with evidence, and oftentimes you need to show someone the right clue to make them talk.

    Between The Lines

    Finally, Between Horizons is also very focused on its story, characters, and dialogs.

    The idea for its premise originated back in 2020. We wanted to explore the concept of one generation setting out to do something and the next generation being presented with the consequences of that (unfinished) journey. We believe it’s a relatable topic to many people especially nowadays, with the younger generation inheriting a host of different crises. The responsibility generations bear for one another is a complex and nuanced problem for which we believe to have found the perfect setting.

    We can’t wait to show you more of the game over the coming months. For now, we hope that you like what you see and that you enjoyed this little write-up of our thoughts behind the game. Follow us on Steam for more updates and most importantly, add Between Horizons to your wishlist! Both would help us out a great deal.

    Thanks a lot for reading, have a nice week!

    Much love, The DigiTales team

  • Lacuna Is Coming To Consoles!

    Lacuna Is Coming To Consoles!

    It’s been a while! Do we have news for you.

    It’s been really hard to keep our mouths shut whenever you guys asked about our plans for consoles over the past few months. The truth is, we were always planning on bringing Lacuna to all major platforms eventually, but we didn’t want to make promises we couldn’t keep. Now the cat is finally out of the bag:

    Lacuna is launching on PlayStation 4/5 as well as Xbox One and Xbox Series X/S on December 21st, and on Nintendo Switch on December 28th!

    The game will be the same across all platforms, but if you’ve been holding out for its console release or want to give it another go, treat yourself this Christmas! There will be a 20% launch discount on Xbox and Nintendo Switch as well.

    Please only refer to us as “award-winning” from now on.

    In other news, Lacuna won Best Game at Game Award Saar last week! It’s our state’s annual awards ceremony for locally crafted video games. Our prime minister Tobias Hans himself lauded Lacuna and bestowed it with the prestigious award and the €10.000 in prize money.

    Lacuna is also nominated in two categories (Best Audio & Innovation Award) at Deutscher Entwicklerpreis, one of the biggest annual celebrations of video games made in Germany. Keep your fingers crossed for us and/or come see if we win live on stream next Thursday, December 9th!

    We moved! Again! And then went back home.

    This September, we finally settled into our awesome new office in the center of Saarbrücken. We share the floor with a few fellow indie developers, and we couldn’t be happier about our decision to move to a bigger place, especially in light of our plans to hire two more people in early 2022. Unfortunately, just a few days ago, the fourth Covid wave forced us back into home office for the foreseeable future – but we will be back!

    We could go on…

    … and we will:

    • Nominate Lacuna for “Outstanding Story-Rich Game” on Steam!
    • Vote for Lacuna in the Indie of the Year competition on IndieDB!
    • Check out our bundle with The Darkside Detective!
    • Visit our booth (hall 2, room 17) at the virtual Indie Game Fest!

    Okay, that’s it. If you want to see more frequent updates, feel free to follow us on TwitterFacebook, or Instagram! We also have a community Discord server if that’s your thing.

    We wish you a wonderful holiday season, and we’ll catch up next year!

  • Lacuna is Out Now on PC!

    Dear guys,

    At long last, Lacuna is out now on Steam and GOG! We can hardly believe it ourselves. Thank you all so much for your support thus far, and for patiently awaiting the game’s release. Check it out:

    Steam

    GOG

    If you want to support us, please consider recommending the game to your friends and leaving a positive review. If you’re on the fence about getting it, remember that there’s an hour-long free demo both on Steam and GOG.

    You know us, we have a new trailer for every occasion, and the release is no different!

    The game actually came out over two days ago, but there was so much going on since release that I didn’t have time to post the news sooner. It’s sitting at 96 Steam reviews already, 96% of which are positive. On its first day alone, the game sold a four-digit amount of copies and had over 5.000 viewers on Twitch. Needless to say, we are extremely happy about this very favorable reception.

    We are also really, really exhausted. The weeks leading up to the release were very stressful, and we’re only slowly recovering. We’ll be around for a few more days to monitor the forums and release patches if needed, then we’re going on vacation for a bit. We’ll be back in mid to late June!

    – Julian

    Lacuna artwork vertical
  • Lacuna Coming May 20 – Prologue Out Now!

    Lacuna Coming May 20 – Prologue Out Now!

    Dear everybody,

    The title says it all, so I’ll keep this short and sweet!

    Check out the new Lacuna: Prologue

    It contains the same levels as the demo we released during the Steam Game Festival earlier this year, but there are two major differences:

    1. It is much more polished, featuring new art, music, sound design, and mechanics.
    2. It has a dedicated page, so you can leave a positive review if you liked it. This way!

    Lacuna is coming out May 20, 2021

    That’s right, screw “summer 2021”. We’re pulling what’s called a reverse Duke Nukem Forever and releasing the game earlier, less than a month from now! If you haven’t already, wishlist Lacuna here.

    We hope you enjoy the Prologue and we can’t wait to share the rest of the game with you!

    Julian

  • 2020 Wrap-Up!

    2020 Wrap-Up!

    Dear reader,

    2020 has been an eventful year for many of us, for better or (mostly) for worse. Here at DigiTales, we were able to hire our first full-time employees in January, and we officially announced our debut title Lacuna in summer. We have been relatively quiet about it ever since – until now!

    Almost content-complete

    With Lacuna being a linear game that only has a handful of mechanics, a large part of our work has been the implementation of its complex story. The flowchart below provides a high-level overview over all the scenes, cases, central clues, and ways in which the player may influence the development and outcome of the story. I blurred it to avoid spoilers:

    The very first version of the story existed all the way back in 2015, and the chart has evolved alongside it ever since, finally hitting version 1.0 the other day. Now all that’s left is some fine-tuning like tweaking the wording, refining the character arcs, and sneaking some more world building into the dialogs.

    In terms of the story’s implementation, we have good news to share as well: It’s all in the game already except for the finale and epilog! We’re expecting to finish the story’s implementation up within the next three months, with a projected word count of about 60.000.

    Demo early 2021

    That’s right: Lacuna will get a free demo! It’s hitting Steam in early 2021 and will be available in English, German, Korean, Simplified Chinese and possibly more languages at launch. In fact, its translation is already in the works. If you don’t want to miss it, come back to our Steam store page some time in February.

    Playtest sign-up

    We’re running a big playtest over the holidays, which will include the first two of Lacuna’s three acts. If you want to help us out as a tester, please sign up here. Please make sure to only sign up if you’ll be able to commit a few hours between Christmas and early January. If you’ve registered before, you’re still on the list! Unfortunately, we cannot guarantee that everybody who signs up can be included.

    Visual upgrades

    In light of the upcoming demo, we’ve taken another hard look at Lacuna’s visuals and decided that they could be even shinier. More specifically, we decided to add character portraits, cutscenes, and a bunch more VFX.

    These are the portraits of most people you will encounter in the demo. Each of them has a set of facial expressions for their respective dialogs. The portraits are being created by the amazing Martina, who has been nice enough to make some time for us between her projects at Radical Fish Games.

    One thing we noticed while playing other adventure games for research was that occasional cutscenes go a long way in setting a mood, breaking up the pace, alleviating visual fatigue, and just increasing the perceived overall quality of a game. That’s why we asked the incredible Weilard to create six beautiful animated cutscene sprites for Lacuna. He finished the first one the other day:

    But that’s not all. Additional particle, shader, and other visual effects created by our fantastic shader magician Simon over the past few months have elevated Lacuna from a pretty pixel art game to a veritable visual feast, if we may say so ourselves. Leaves blown by the wind, fog billowing on the water, fire, smoke, rain, leaves, reflections, and more really bring its atmosphere to life.

    Next project

    Even though we will be working on Lacuna for a few more months, we already need to think about what we’ll do next in order to avoid too much downtime between the projects. That’s why we’ve already cooked up a new concept we’re going to turn into a prototype next year. To that end, we recently received €20.000 in funding by Game Base Saar (a local funding program)! Here’s a tiny sneak peek, we don’t want to give away too much:

    I almost feel bad for writing this, but 2020 has actually been a good year for us overall. The death, despair and political turmoil around the world have of course affected us and the social isolation is getting us down, but we’re also aware of and grateful for the privileged positions we’re in. We were able to switch to home office indefinitely without fear of exposing ourselves to the virus every day or losing our source of income. We worked on our game full time, which is a dream come true, and mostly managed to stick to our deadlines, a rare occurence in game development and much more so for a debut project. I’m really proud of what we’ve achieved this year despite everything that 2020 has been throwing at us, and I can’t wait to see what we can do once things are back to normal.

    Anyway, make sure to sign up for the playtest if you want, and keep an eye out for Lacuna’s demo in early 2020. Also, subscribe to our newsletter to get future updates like this delivered to your inbox! We use it very sparingly, promise.

    We wish you an adequate holiday season given the circumstances. Things are about to look up, I’m sure of it. Stay safe, merry Christmas, happy New Year, see you in 2021!

    Julian

  • Lacuna Devlog: Game Design

    Lacuna Devlog: Game Design

    Welcome to our very first devlog! Let’s catch you up on the past five years of Lacuna’s development.

    Just kidding; Lacuna’s first prototype did exist back in 2015, but we’ll spare you most of the details of its long journey. Instead, we thought that we’d focus on its somewhat unique game design.

    Gameplay

    Since the game isn’t out yet, we want to start by telling you what it will generally be like. Let’s attempt a description without marketing lingo: Lacuna is a story-driven adventure game with platformer controls and investigation elements. Its four fundamental gameplay types are dialogs (with choices), moving around, examining objects, and solving puzzles. All of them are staples of the adventure genre, especially point & click games, but each one is a little different than you might expect:

    Dialogs in adventures often loop back to one central node where the player can select all the options they didn’t previously select. In Lacuna, each dialog can only be played a single time. Dialogs branch based on player decisions, which oftentimes lead to consequences later in the game – some big, some small.

    Movement in Lacuna can be described as “platformer controls without jumping”. Pointing and clicking tends to feel too strategic, somewhat removing the player from the action. We found a direct control scheme (using WASD or a controller) more modern and immersive. However, since the game is primarily aimed at people interested in experiencing the story and solving puzzles, Lacuna never demands quick reactions or precise hand-eye coordination.

    Examining is one of the basic actions of any P&C adventure. In Lacuna, the player uses Investigation Mode to investigate nearby objects that might be of interest. They are recognized and highlighted automatically because we don’t consider searching things a particularly interesting type of challenge. Having a defined array of available objects to toggle through also lends itself to controller-based input (more than being able to select any pixel on the screen hoping it will react in some way).

    Puzzles in adventures are often self-contained mini games testing the player’s dexterity or logical thinking. Alternatively, especially in P&C games, they involve handling and combining items. Both types have a tendency to not make sense in the game world and/or not tie into the story in a meaningful way. By contrast, all puzzles in Lacuna are mysteries directly arising from the story and dialogs. The player solves them via dialog choices, investigating evidence, and ultimately via a central mechanic dubbed Case Sheets (more on all those mechanics in our example down below).

     This is the game’s first Case Sheet, very short and to the point

    Game Design principles

    From the very start, we laid out some design principles we’ve been trying our best to follow ever since. Some of them have become somewhat apparent in the previous paragraphs (e.g. unique dialogs, story-focused puzzles), but there’s a few underlying, abstract ones worth highlighting.

    The first one is no takebacks: We wanted to make choices and their consequences front and center in the game’s design and story. We felt like manual saving and loading would take away from that, and we opted for an automatic save system instead that always overrides your previous save file (in that slot). Non-replayable dialogs as described above were another consequence of this design principle. Not being able to go back might be frustrating at times, so to somewhat make up for it, we created three save slots allowing for multiple concurrent playthroughs. Plus, the game only saves after each level (i.e. every few minutes), so if you immediately regret something or made a choice by accident, you can undo it by reloading the level right away.

    We also decided to give the player very limited feedback. As much as we like some of Telltale’s titles, we were never fans of “x will remember this”. It yanks you right out of the action by giving you explicit information that your avatar doesn’t have (and that seemed to mostly be a smokescreen anyway). Lacuna gives no hints of this sort and doesn’t disclose which decisions matter more than others, quietly adapting to the player’s behavior. That being said, the reasons for certain consequences need to be made clear later to mitigate frustration.

    There are some more principles related to writing, but they’re a topic for another day. Let’s skip ahead and get to the big one. It’s so big, in fact, we need to give it its own headline:

    No getting stuck

    This design principle has been making our lives so much harder (but our game better) ever since we came up with it.

    The thought process behind it was simple: In games with both a story and puzzles (e.g. most P&C games), story progress is almost always tied directly to puzzle progress. Until you solve the puzzle at hand, you don’t get to see the next part of the story. For some players, especially those most interested in the story, this can become a problem. If they’re stuck for too long, there’s a chance they’ll just drop out and never pick the game up again. Even if that doesn’t happen, hard puzzles always run the risk of messing up the story’s pacing and interrupting your immersion in the game – because you’re becoming frustrated or, even worse, because you decide to tab out and Google the solution. To avoid people getting stuck, we considered a number of solutions:

    Solution 1: Make the puzzles very easy?
    This isn’t our favorite since it somewhat defeats the purpose of puzzles. They’d still play a role as a change of pace now and then, but if puzzles aren’t a little hard, nobody will feel like a detective solving them. Some early puzzles in Lacuna are easy, but most aren’t.

    Solution 2: Provide hints?
    Hint systems can be found in many adventures featuring puzzles. Unfortunately, they often take the player out of the experience in one of three ways: In some cases, the hint is provided by extradiegetic UI (e.g. in the pause menu) and therefore seems to come out of nowhere in the game world. In other cases, the player character is the one giving the hint, disconnecting the player from their avatar’s perspective. The third option of NPCs providing hints is a little better; however, it is often hard to justify why an NPC would be able to point the player in the right direction without possessing the rest of the solution to the ongoing puzzle (and why they didn’t volunteer it in the first place). The two types of (sort-of) hint systems we went with in Lacuna are Highlight Mode, which displays optional outlines around objects and NPCs that hold new information, and redundant information, meaning that sometimes the player is given two ways of obtaining an important clue.

    “YOU ARE PLAYING A GAME RIGHT NOW”

    Solution 3: Decouple story progress from puzzle progress?
    Why not simply make a story-driven game throughout which the player can solve the occasional puzzle if they feel like it? Well, because it would require that puzzles be somewhat detached from the story. As a result, they run the risk of feeling meaningless since solving them is not rewarding and failing is not punishing. However, this can work quite well when combined with…

    Solution 4: Make branching content for different solutions?
    Instead of impeding the player’s progress, wrong or missing puzzle solutions could lead to a less desirable continuation and/or outcome of the story. Unfortunately, creating a new story branch for each and every wrong solution to a puzzle is hardly feasible. However, there are less extreme ways of realizing this. For instance, the game could account for the player’s overall puzzling performance at certain points in the game, e.g. trigger the “good” finale to an act if they got more than x% of the puzzles right, and the “bad” one if not. There could also be cascading consequences of sorts, e.g. solving one case correctly may give the player an edge in a later one. These approaches have similar downsides as optional puzzles do, but to a lesser degree; puzzle success no longer being required for progress makes them feel more detached from the story and removes immediate feedback. Regardless, we have found this to be the best solution, which is why we employ it quite a bit in Lacuna (while trying to avoid all the pitfalls). By the way, if all of this is becoming too abstract for you, bear with us! The second half of this post is all about a real example from the game.

    Detroit: Become Human offers an astonishing number of different outcomes depending on player action, but not everybody has that kind of money to burn

    Despite all of these measures being taken to make sure that the player won’t get stuck, Lacuna can still be called a hard game. While it’s not difficult to get to the end, it’s pretty difficult to get a good ending and not mess things up on your way there. In other words, rushing through the whole story is possible if you don’t mind bringing it to a terrible conclusion.

    Detective game problems

    Many of the problems we encountered when designing Lacuna are as old as detective games themselves. Most of them are centered around how the player and the game communicate with one another, and especially how the player conveys their thoughts to the game. Several principles have proven to make for a good experience (across countless approaches to this problem over the years), some of which have made their way into Lacuna:

    Principle 1: Many channels out, few channels back in.
    If the game conveys information to the player on many different channels and in many different ways, the process of piecing the solution together tends to feel more interesting and rewarding. In Lacuna, the player picks up clues from dialogs, objects, environments, the news, and e-mails (with all sorts of attachments). At the same time, the channels via which the player communicates that solution back to the game are kept to a minimum, namely Case Sheets and (to a lesser degree) dialog choices. Having one or two central mechanics for player input makes the experience more coherent and transparent and facilitates designing the mysteries around it.

    Return of the Obra Dinn by Lucas Pope provides a bunch of different sources of information, but just one central mechanic for the player to communicate back to the game

    Principle 2: Have the player communicate only the solution.
    It is near impossible to create a system through which the player communicates to the game how they arrived at a solution. Luckily, this is not necessary. A well-designed puzzle provides all the information, then moves the entire solution process solely into the player’s head, and finally prompts the player to input only their answer. The player’s objective should be stated clearly, but in a very general way at the start of a case (e.g. “find the culprit”).

    Principle 3: Give the player maximum freedom in communicating the solution.
    The way in which the player communicates the answer to the game is the most crucial part to get right. One aspect is to give the player many choices (or a large combination of choices) to pick from. Two things should be avoided: 1. Giving the player a high probability to succeed by picking a random answer. 2. Making it easy for the player to guess correctly because only one or a few of the available answers appear plausible. An example for a bad solution like this would be to give the player three dialog choices to solve the puzzle; even worse would be if one of them obviously made the most sense. A better approach would be to give the player a cloze text (which is what Case Sheets boil down to) with a bunch of plausible options for each gap. Another possibility is to have the solution be an unguessable string of characters that the player needs to enter manually. Both ideas utilize combinatorial explosion to make guessing and brute-forcing nearly impossible, and both of them can be found in Lacuna.

    Good luck brute-forcing your way through Detective Grimoire’s cloze texts

    Puzzle example

    This has been a lot to take in, but hopefullly it’ll all become crystal clear when put to concrete use! The following is a level the player encounters early on in Lacuna. It won’t reveal much of importance about the story, but it will spoil the solution to this one puzzle, so consider yourself warned.

    Since this is essentially the game’s first puzzle, it won’t contain some of the difficulties added later (like a large number of channels communicating potential evidence). In harder cases, the player will need to have paid attention to testimonies, news articles etc. from earlier levels to arrive at the correct conclusion, and some cases span multiple levels. Not this one, though; all the information required to solve it is directly contained in the clues and dialogs of the one level where it starts and ends.

    The puzzle

    Here’s what happens: Our protagonist Neil is called to a crime scene to investigate a murder. His colleague Gary explains that a sniper must’ve taken the fatal shot from one of the opposite highrisers. He mentions that one of them, the casino, is holding a large event with lots of security, so it probably wouldn’t have been the sniper’s first choice. Neil is also told that the bullet hasn’t been found yet.

    At this point the player is given the Case Sheet, allowing them to submit their solution at any time from now on. This way, the player cannot know when exactly they possess enough information to solve it correctly. It also allows them to just submit any random solution if they completely get stuck and want to get on with the story. The Case Sheet simply reads “The shot was fired from ____”, which doesn’t spoil the solution while also formulating the question very clearly. There are only four options to pick from, which does make the answer more guessable, but we decided that was acceptable for the first puzzle, especially given that the player has only one shot.

    Puzzles

    The player can now walk around the building freely, investigate objects and talk to witnesses. Some of what they learn may be fluff, world-building, or even red herrings, but some is important evidence. The body lies out on the balcony, from where four buildings are visible: Gadle Hotel, Pixie Casino, Sakura Hotel, and Rocket Tower (from left to right). By investigating them, the player learns their approximate distance to the balcony. The position of the body, head to the left, already indicates that the shot probably came from somewhere on the right. A witness inside who saw the body hit the ground corroborates this.

    The player may notice that they can also investigate a cupboard next to the balcony door. Its description states that the bullet disappeared into the wall on the other side and that it is locked. Investigating it unlocks a new dialog with a witness who gives the player the keys. They open up the cupboard and find the bullet. Neil’s colleague takes a look at it and surmises that it came from a rather small and quiet rifle that cannot shoot accurately over distances greater than 200 meters.

    The player asks for the key in the newly available dialog

    Gadle Hotel probably appears unlikely at this point because it’s too far on the left. The casino is also on the left and would additionally be a bad pick due to its increased security that night. This leaves Sakura Hotel and Rocket Tower, only the former of which is close enough to be plausible. (Remember that the player learned both the rifle’s effective range and each of the buildings’ distances to the balcony earlier.) At this point, the player may decide they’ve seen enough and submit the Case Sheet.

    Processing the answer

    Now the game evaluates if the player got the answer right. If so, that’s perfect: The player’s correct answer causes the story to continue, and they head off to Sakura Hotel to look for the sniper. But how do we handle it if they picked one of the other three buildings?

    You might be thinking that we could prevent the Case Sheet from being accepted until it’s correct. Although not unheard of, that’s a bad idea because:

    • It allows brute-forcing. And if it didn’t (by introducing combinatorial explosion), it would allow getting stuck.
    • It doesn’t make sense that the Case Sheet or the person reviewing it knows the right answer and is withholding it.

    Then how about we trigger a game over state if the submission was wrong? Having to replay the level would at least discourage brute-forcing. The thing is:

    • We’re trying to avoid game over states since they break the immersion (in any game).
    • It’s frustrating chicanery; remember that we want people to be able to get on with the story whenever they want.
    • It contradicts “no takebacks”. You submitted the wrong answer, you should have to live with it.

    Okay, next idea. We could make all the incorrect buildings levels of their own and have the player go there to eventually realize they’re in a dead end… But we shouldn’t, for a number of reasons:

    • It destroys the story’s pacing.
    • It’s a lot of extra work most players will never get to see.
    • The player may take a long time to realize they’re in the wrong place and will then have to be steered in the right direction anyway.
    • It can’t possibly be applied to later cases, where Case Sheets allow for 256 (4x4x4x4) or even more possible answers.

    As is the nature of game design, what we did end up doing is the least bad solution with the most acceptable drawbacks. Here’s what happens: If the player picked the wrong building, everything seems fine until they get into a train to their destination. They then get a call about an anonymous tip made by someone at Sakura Hotel and are told to go there instead. This tip (and especially the reason for it being anonymous) is itself a piece of evidence for the puzzle at the hotel, already setting up the next mystery. This hopefully somewhat turns the attention away from the fact that the player just got railroaded back to the correct path. The player is also told that someone else from their squad will follow up on the building they (incorrectly) selected, and they will later learn that it turned out to be a dead end. Plus, Gary will make a snarky comment about the player’s misstep in the next level. These are some small ways to make the player feel like their decision wasn’t completely irrelevant.

    And in fact, it wasn’t: Here’s where cascading consequences come in. Each and every correct puzzle solution throughout the first act may produce a piece of evidence relevant to the act’s overarching case. This means that every incorrectly solved case makes it more likely (or even inevitable) that the player will fail the bigger ongoing case. Failing the first act’s overarching case will in turn lead to information not being obtainable in the second act, which in turn prevents the player from getting one of the two “best” endings to the game. This cascading system makes sure that even small failures can have big consequences even though the player is immediately railroaded into the correct path at the time of their misstep.

    Of course, this wouldn’t be game design we’re talking about if these cascading consequences didn’t come with their own set of problems. Most notably, the player may find it unfair or, even worse, not realize at all that they cannot know the solution to a later puzzle due to having failed an earlier one. But although we have come up with some solutions for this problem as well, let’s not go down that rabbit hole today.

    Have a look at this censored overview of just the second act: bubbles are scenes, and the thinner lines between them represent only the most important (cascading) consequences

    Did we stay true to our design?

    Let’s quickly go over all the solutions and principles mentioned in the first half and see if we managed to apply them to our case.

    Lacuna’s design principles

    No takebacks? Check. The player has one shot at submitting the Case Sheet and will have to live with the consequences.
    Limited feedback? Check. The player isn’t told (right away) if their answer was right or wrong, even though they’re always sent to the right building.
    What about no getting stuck, did we apply any of the provided solutions?
    Did we make the puzzle easy? Yes, but only because it’s an early one.
    Did we decouple story and puzzle progress? Yes, the player can always submit their Case Sheet, which ends the level and starts the next one.
    Did we make branching content for different solutions? Yes, in two ways: small changes in dialogs and, more importantly, our system of cascasing consequences.
    Did we provide hints? Sort of. We did the two things described above: use Highlight Mode and redundant information. Let’s briefly talk about how exactly:

    Highlight Mode, which enables colored outlines on evidence and people holding new information, is explicitly introduced at the start of this level. It helps the player notice a number of important things: that they can investigate the cupboard; that a new dialog becomes available once they start looking for the key; and after that dialog (in which the player obtains the key), that the cupboard holds new information because it can now be opened. Highlight Mode also acts as an indirect way of telling the player that they can solve the puzzle and stop looking for clues at a certain point, as there are no colored highlights left on any of the people or objects. Of course, Highlight Mode is optional, so players who like a challenge can just leave it off.

    There are also some redundant pieces of information. For instance, the player might already have the idea that the sniper was somewhere on the right based on just looking at Banny’s body. However, the dialog with the secretary confirms this again. Similarly, the player might think that Pixie Casino is far enough on the right to still be a possibility, but Gary telling them about the increased security there should make it clear that it’s not the right answer.

    Detective game problems

    Now for the detective game problems and their solutions discussed above.

    Did the game communicate on many channels? Not particularly since this is an early, easy case. The player only has investigatable clues and witness testimonies to work with.
    Did the player communicate back on few channels? Yes, the solution to the case is submitted via one central mechanic, a Case Sheet.
    Did the player communicate only the solution? Yes, the Case Sheet does not ask how they arrived at the conclusion.
    Did the player have big freedom communicating the solution? Not really, but we found it acceptable here as there’s still only a 25% chance of guessing it. A high number of solutions is much more crucial for games that allow infinite retries and thus lend themselves to brute-forcing (instead of giving the player just a single shot like Lacuna does).

    Final thoughts

    Detective gameplay is hard to get right and it’s naturally at odds with a game’s story in a number of ways. We’re lucky to be building upon so many experiments by countless other game developers teaching us what works (and what doesn’t), and it’s our hope that we’ve mixed an interesting and unique cocktail of ideas that will keep you engaged and entertained throughout our game. If you want to see for yourself, wishlist Lacuna now and give it a spin when it comes out.

    If you have any thoughts on the topic or resources to share, let us know! We’re happy about any opportunity to nerd out about game design.

    Julian

  • Announcing Our Debut Title Lacuna

    Announcing Our Debut Title Lacuna

    The day has come.

    We just announced our first title: Lacuna – A Sci-Fi Noir Adventure. Its Steam page is now live, which feels a little surreal. The game will be published by Assemble Entertainment, a fantastic German publisher who believed in the project from its very start.

    Much more on the game is to come in the following months. We’ll show some gameplay soon and go in depth about different aspects of it in a series of devlogs. Right now, go check out the new trailer, artwork and screenshots, and most importantly: wishlist and follow the game on Steam. This way!

    We want to take this opportunity and say thank you from the bottom of our hearts to everybody who has been part of Lacuna’s journey so far.

    Julian

  • Future-Proof 2D Render Order

    Future-Proof 2D Render Order

    Once you start working on a 2D game, the question of how to set up a proper render order will come up very early on. Render order, at least in the way I will be using the term, determines which parts of your 2D game get rendered (drawn, displayed, shown) in front of (i.e. later than) other parts.

    Unfortunately, you won’t be able to figure out the requirements for a good system until after you need it. I just spent almost a week changing ours from what I thought would be good when we started our current project to what many months later turned out to make a lot more sense. Here’s hoping that our findings will save you the trouble of going through that painful refactoring process and help you get it right from the start.

    Please note:

    1. Some of this makes sense for any 2D game, but some will be specific to a perfect side view typical for side-scrolling games.
    2. This system accounts for real-time lighting; if that’s no concern of yours, a simpler system might do.
    3. It also accounts for multiple vertical layers, some of which are only revealed after a foreground sprite fades out. If your game doesn’t have this, parts of our system will be irrelevant for you.
    4. I will be using some Unity-specific terminology like “Sorting Layer” and “Order in Layer”. Nevertheless, most of this article should be helpful on a conceptual level regardless of game engine.

    1. Quick introduction: Render Order Terminology (in Unity)

    In case you’re working in Unity and are completely new to the topic, I thought I’d provide a short explanation of Unity’s Render Order system (as of Unity 2019.3.13) and give you a quick intro into its usage. If you already know it, skip right ahead to Best Practices below.

    The following assumes that you actually work in a 2-dimensional space and aren’t using the Z axis to determine Render Order. If you want your sprites to use Unity’s 2D Render Order system instead of rendering based on your sprites’ Z positions, you need to select your camera and set its Projection to Orthographic instead of Perspective.

    1.1 Sorting Layers

    Once that is done, all your sprites will be rendered based on Sorting Layers (“SL” for short). These work essentially like layers in Photoshop or any other image editing software: All sprites on one SL will always be rendered in front of all sprites on another, thus adding depth on an “imagined Z axis”.

    Let’s have a look at the available Sorting Layers. Add a SpriteRenderer component to a game object and select the Sorting Layer drop-down.

    Hit Add Sorting Layer… at the bottom to open the Tags and Layers window. It shows all existing SLs and allows you to add any number of new ones. “Default” cannot be deleted, but you don’t have to use it. The SL at the top of the list is rendered at the very back, the SL at the bottom is all the way in the front.

    Most of the advice under Best Practices pertains to which layers you should set up, so I won’t go into detail about it here. However, here’s a Unity-specific tip: When adding new SLs, use a slash to nest them. Nested layers will then appear neatly grouped together in the SL drop-down on your Sprite Renderers. In the example below, we distinguish between middleground (MG) and background (BG), each of which encompass several SLs:

    Once your SLs are set up, you can assign any sprite in your scene to them by selecting its SpriteRenderer component and picking the desired SL from the drop-down menu.

    1.2 Order in Layer

    Within each SL, sprites are again organized into an Order in Layer (“OiL” for short). While SLs have names, the OiL of a sprite is indicated as an integer. The higher it is, the further in front a sprite is rendered. Consider the following example containing three SLs: “Background”, “Middleground”, and “Foreground”.

    SpriteSorting LayerOrder in Layer
    SkyBackground-1
    MoonBackground0
    SkylineBackground1
    HouseMiddleground-1
    Player characterMiddleground0
    ColumnMiddleground1
    LaundryForeground0

    Even though sky, moon, and skyline are all on the background layer, they are rendered in the correct order because of their Order in Layer. As you can see, an OiL can have a negative int value.

    2. Best Practices

    At this point, you might think that you know all you need to render your sprites in the right order. If you’re going for a more simple look, that might be the case. However, if you want to go the extra mile (especially if you have dynamic lighting), here are some more specific tips for setting up Sorting Layers (SLs) and Orders in Layer (OiL) in a future-proof way. And even though the terminology comes from Unity, much of the following advice should apply to any engine on a conceptual level.

    2.1 Background and Foreground

    For our project, we have decided to distinguish between background (BG), middleground (MG), and foreground (FG). This is merely a conceptual distinction; on a technical level, each of these is made up of multiple Sorting Layers. We also added a Sorting Layer named “Dev” for sprites that we use for development only that’s invisible to the player. Something we didn’t do (but might in the future) is add another visible layer for all World Space UI to ensure that it renders in front of all non-UI sprites.

    Hiding and showing the three main groups of Sorting Layers (BG, MG, and FG) using a custom script

    No matter how you end up doing it, one thing to keep in mind when setting up SLs is lighting. Each 2D Light component in Unity can be set to affect or not affect any number of Sorting Layers:

    This means that the system determining render order and the system determining which lights affect which sprites are intertwined. This may cause problems; for example, let’s go back to the example shown under Sorting Order above: If you add a light to brighten the moon, it will inevitably (within its range) also brighten the sky and skyline because all three of them are on the same Sorting Layer. You might not want one light in the background to affect another background layer that seems very far away from it (on the imagined Z axis). Luckily, this can easily be resolved by making the system more granular, i.e. by adding more Sorting Layers. The moon and sky could remain on the same SL and the skyline could be put on a new one that’s visually closer to the player. This way, the moon would light up the clouds, but not the skyline.

    For our game, we created 7 Sorting Layers for the background alone. We ended up with that number specifically after experimenting and deciding that 7 visually distinct layers looked alright. You’ll notice that some background layers contain a big, semi-transparent sprite that adds atmospheric tint:

    Incidentally, we got another usage out of those 7 background layers by giving each one its own parallax speed. (Creating a parallax effect without the need for a Z axis is a topic for another day.)

    Some of those SLs may be further subdivided into Orders in Layer. For example, this street in the background is made up of three parts in itself: handrail in front, NPCs in the middle, and the rest in the back.

    We also added a “negative parallax” layer to the very foreground that moves in opposition to the camera and the parallax layers in the background:

    The middleground is where it gets complicated, so let’s give it a closer look!

    2.2 Middleground

    The middleground (at least in our terminology) is where all the action takes place, where the player and NPCs live, and as a result, where things move around a lot. I’ll first explain how we set it up and then elaborate on the reasoning behind it.

    2.2.1 Sorting Layers

    This next distinction might appear arbitrary at first, but it is in fact the ideal result we arrived at after a long series of experimentation: There are generally two types of Sorting Layers in the middleground; “movement layers” where the player and NPCs move around (called MG/middle and MG/back), and “wall layers” separating them (called MG/frontwallMG/middlewall, and MG/backwall). Think of this latter type as the “walls” sandwiching the middle and back SLs between each other (even though they contain many sprites not specifically depicting walls).

    The player and NPCs can only move on MG/middle and MG/back and sometimes switch from one to the other. Differently put, they can be in “middle areas” (where they are rendered on MG/Middle) and “back areas” (where they are rendered on MG/back). The distinction between these two isn’t always immediately apparent and may seem open for interpretation. When does the player transition from middle to back? The quick answer is: Whenever they “pass by” a layer of sprites (specifically MG/middlewall) that rendered behind them before and now renders in front of them, i.e. when they walk past a wall layer.

    By doing so, the player may enter the bounds of a sprite on the wall layer inbetween that then fades out. We call these “facades”, and one can be seen in the first example below.

    Moving up: Buildings and areas entered via frontal stairs from below are usually considered as the player entering a back area because the player walks “further into” the screen or “away from the player”, behind the wall layer they were previously in front of:

    However, this isn’t necessarily the case when moving up. In the example below, the player stays on MG/middle the whole time. That is because they never pass by a wall layer; the facade sprite that fades out was in front of them the whole time (on MG/frontwall), as you can see from the columns at the bottom:

    Other movements: Entering a building or area from the side or top is different as the player never goes “further into” the screen, thus never bypassing a wall layer, no matter if they walk normally, use stairs, or walk through a door. This case never counts as entering or leaving a back area because no movement on the imagined Z axis occurs.

    Teleporting: Teleporters moving the player from an open area into a room or building aren’t all the same. If the player is transported behind a sprite that was previously behind the player (again: if they move past MG/middlewall), they are indeed moving from MG_middle to MG_back:

    However, if the player does not move past a sprite that was previously behind them, they stay on the same layer. In the example below, the player teleports from MG_middle to MG_middle.

    2.2.1.1 What to look out for

    After telling you what worked, I also want to talk about the mistake we made with our Sorting Layers that caused me to rebuild the system completely to the way it is now. I urge you to read this bit as it might save you a lot of headache!

    The render order we used to have included only the Sorting Layers MG/front/middle/back/veryback. However, this fell apart with the introduction of 2D Lights: In certain scenarios, a sprite needed to be rendered on a certain layer but wasn’t supposed to be affected by a light source that applied to other sprites on that same layer. This mostly affected facades; consider the example below:

    Example setup; sprites have been moved on the Z axis only for demonstration purposes

    The player needs to be rendered in front of the facade while outside the building. Therefore, since the player is on the (formerly) MG_middle layer, the facade needs to be on the same or a lower middleground layer to be rendered behind the player; it cannot be on MG/front.

    Example: outside (MG_middlewall)

    Example: inside (MG_backwall)

    However, light sources inside the building apply to (formerly) MG/middle as well as (formerly) MG/back to light up the correct sprites on the inside, behind the facade. As a result, lights rendered behind the facade appear to fall on the front of the facade, which shouldn’t be the case.

    That’s why the facade has to be on another layer between the player and the regular MG_middle layer, which is only affected by lights in front of it. To allow for this, we needed to add more layers.

    Unfortunately, simply adding more layers only half-solved the problem because sprites like the player and NPCs may switch from middle to back and vice versa. As a result, any light on one of those layers will either not affect those moving sprites or always affect them, including in the wrong scenarios (i.e. through walls). That is why we came up with the distinction between middle areas and back areas as well as the wall layers between them. Moving from one to the other changes the moving sprite’s Render Layer to MG/middle and MG/back respectively (simply based on entering trigger colliders). As a result, a light source affects them when they’re right next to it in the same room – but not when they’re right next to it but with a wall-inbetween.

    2.2.2 Order in Layer

    Similarly to the background, each Sorting Layer in the middleground is in itself divided into multiple Orders in Layer (OiL). Here’s what we learned about using those.

    2.2.2.1 Keep OiL the same across SLs

    The “wall layers” are each structured very similarly to one another, meaning that one wall layer’s OiL values are (almost) exactly the same as all the other wall layers’ OiL values. The two movement layers also share the same OiL values. This makes memorizing OiL much easier and considerably speeds up placing sprites in the scene. For example, one type of sprite (e.g. foliage) has the same OiL across all wall layers on which it occurs. Make sure to set up external documentation for these general OiL rules (and your Render Order in general)!

    2.2.2.2 Reserve OiL

    In our project, some of the sprites can be interacted with (we call these “Interactables”) and show outlines once the player gets in range. The outlines need to be rendered behind the Interactable, but in front of the sprite behind it. This means that one OiL between the Interactable and the sprite right behind it needs to be reserved for the outline to prevent “fighting” for render order between the outline and the background sprite. For instance, if an Interactable (e.g. a cigarette vending machine) is on OiL 1 and the sprite behind it (e.g. a wall) is on OiL 0, the outline of the Interactable would have nowhere to go in-between the two. That’s why we have now reserved certain OiL for certain renderers: The OiL of any sprite must always be set to a multiple of ten (i.e. end in 0); outlines always render 1 OiL behind the sprite they belong to (i.e. end in 9). In our example, the cigarette vending machine could be on 10, the wall behind it on 0, and the vending machine outline on 9. Particle effects also often overlap with multiple sprites in uncontrolled ways, so we always place them on an OiL ending in 8 to avoid “fights”. Layers ending in 2 to 7 are still free for future special usage if the need arises.

    2.2.2.3 Dynamic OiL

    Most moving sprites in our game, like characters, update their Order in Layer every frame depending on their y position. This has to happen in order for them to render in the correct order relative to one another (on the same SL) where movement on the Y axis occurs, e.g. on stairs. In the example below, the player character renders in front of the NPCs above it, but behind the NPCs below it.

    Simply binding the OiL to an object’s y position – the further up it is on the screen, the lower its OiL – might be enough depending on your setup. However, you’ll likely run into a couple of problems:

    Problem 1: This might not work for special situations, e.g. complex set piece animations with lots of moving parts.
    Solution: That is why we recommend adding a boolean so you can turn this functionality off if you ever desire to set the Order in Layer manually instead.

    Problem 2: What about the rule that sprites can only be rendered on OiL that end in 0?
    Solution: To ensure that this is the case for moving sprites as well, just multiply the OiL by 10. (Concrete formula below.)

    Problem 3: Calculating the render order of sprites based on their relative position only works for sprites that rest on the floor.
    Solution: If your sprites can jump, float, or fly, things get a bit trickier. The short answer is: take the distance between a sprite and the ground platform it relates to into account when calculating the sprite’s OiL. This wasn’t a problem for us because we have no jumping mechanics, so I won’t go into more detail about it. However, this great blog post does!

    Finally, here’s our complete OiL calculation formula (located in the Update() method) followed by a quick explanation:

    movingSprite.sortingOrder = -1 * (Mathf.RoundToInt(transform.position.y * 100f) * 10);

    ElementReasoning
    movingSprite.sortingOrderThe sprite’s Order in Layer value.
    -1Sprites higher up are actually further in the back, i.e. the higher the y value, the lower we want the OiL to be.
    Mathf.RoundToInt()The OiL needs to be an integer. Use rounding rather than casting to int, which only chops off the decimals.
    transform.position.y * 100Take the sprite’s y position down to the second position after the decimal point before rounding. (bigger order of magnitude = more precise)
    * 10Make sure the sprite’s OiL always ends in 0 as established above.

    This is what it looks like in the game. Note that the OiL at the top changes as the player moves up and down.

    And that’s how we do it!

    I’ll talk more about properly setting up 2D assets, e.g. creating a parallax effect and how to handle pixel art, in future blog posts. If you found an error in this post or see room for improvement, I’ll be happy to update it. And if you found it useful, feel free to share it!

    Cheers,
    Julian

  • Stairs in a 2D Side-Scroller

    Stairs in a 2D Side-Scroller

    Preface

    Movement on stairs has been the bane of my existence for a long time. I first added it to our old prototype in late 2017 and the code stayed more or less the same up until recently. It was barely good enough for our prototype, and it was never meant to stick around until release.

    However, precisely because it has produced so many bugs and highlighted so many pitfalls, I can now tell you what to look out for when designing your own system. I say “designing” because I will talk about game design a lot and address programming only conceptually rather than providing actual code snippets. Otherwise, this already lengthy write-up would definitely grow out of proportion.

    First, I want to quickly describe what the key features of our movement system are. If yours has completely different requirements, the solutions I am about to lay out may not work for you.

    1. The game is a 2D side scroller allowing the player to walk and sprint. It has no jumping or dashing, meaning that stairs can only be entered at the very top and bottom.
    2. Movement on stairs has its own custom animations rather than re-using the ones for regular horizontal walking (and sprinting).
    3. There are two types of stairs: frontal (vertical) and side (diagonal) stairs. The latter can descend (top left to bottom right) or ascend (bottom left to top right).
    4. All steps have the same size to fit the player’s animations. Stairs can be any length.
    5. Our player model’s collider (relevant to stairs movement) is around the thighs. However, this is not a requirement for most of this to work on a conceptual level.
    6. We don’t have any combat or other factors that might apply forces on the player while moving on stairs.
    7. The goal is to create intuitive, bug-resistant, and good-looking stairs movement.

    With all that out of the way, let’s iterate and solve problems as they pop up! We’ll start with side (i.e. diagonal) stairs; as you will see, most of the rules we’re about to set up apply to frontal (i.e. vertical) stairs as well.

    Part 1: Getting on the stairs

    The first thing we’re trying to do is get the player on the stairs. Let’s create the very basic iteration 1 of our stairs: When the player is about to pass by the stairs, they hit a collider (we’ll name “bypass check”) that switches them to stairs controls. Place another bypass check at the other end of the stairs and switch back to normal controls.

    The player collider is shown in green, the bypass check is blue.

    The first problem we’re going to solve might appear exotic at first, but it’s actually pretty important to think about early on: The player might not hit the collider at the right moment, especially at a low frame rate if your player movement speed is FPS-independent (which it definitely should be). With fewer frames per second, the walking distance between frames increases, and the higher it gets, the more the player will overshoot.

    At high FPS, the player will hit the collider just as they get in range:

    However, at low FPS, they they may hit the bypass check with just the back of their collider:

    This will cause the walk animation not to fit the stairs sprite. That is why, in iteration 2, we must pick one of two solutions for this:

    1. Simply teleport the player to the right position when starting (“entry point”) or ending (“exit point”) movement on stairs. Since the teleport distance is only bigger at low FPS, when the game is already choppy, the teleport won’t be noticeable.
    2. Have physics calculations happen between rendered frames. This will automatically solve this problem because the collision is always registered in the exact right moment. If you use Unity, FixedUpdate() will take care of this for you, which is called a fixed number of times per second regardless of visible FPS (by default 50 times, but you can change this value to your liking).

    Regardless of the solution you choose (the second one being preferable), I will keep using the terms “entry point” and “exit point” in the rest of this article, as those are conceptually revelant either way.

    The arrow illustrates a movement from one frame to the next.

    Now on to less obscure problems. What if the player wants to be able to walk past the stairs? So far, the player will always enter them once they touch one of its colliders.

    To solve this in iteration 3, let’s start by defining a different collider (called “entry range”) around the start of the stairs. Only when pressing up (at the bottom) or down (at the top) will the player start walking up or down the stairs. Moving left and right makes them walk past the entry points, and they continue with their regular horizontal movement.

    The problem now is that once the player starts pressing up or down inside the entry range, the player will start moving up exactly from where they are, not from the entry point. And even if they teleport to the entry point first (because we implemented that in iteration 2), the teleportation to the starting position looks way too obvious because the collider is so wide:

    To solve this in iteration 4, we make the character walk to the actual entry point while the player holds up or down inside the entry range. E.g. if the player is right of the entry point at the bottom of the stairs, holding up will cause the character to walk to the entry point first before they enter the stairs. For this to work, we need to add some conditional extra controls to our regular movement controls that only apply within entry range.

    Arrows in squares show which button the player is currently pressing.

    Now of course we got rid of the teleporting for good and, as a result, we’ve again lost control over where exactly the player enters the stairs, so the animation looks off. That is why, in iteration 5, we combine entry range and bypass check! However, the latter needs an upgrade now that the player can approach the stairs from both sides (as they can walk past them). The bypass check needs to dynamically switch to the opposite side of the stairs’ entry point, so the player is just in the right position when they hit it from either side.

    The combination ultimately works like this: If the player is within entry range and holds up/down, they move towards the entry point, and then when they hit the bypass check still holding down the button, they start moving on the stairs.

    Note: The bypass check always switches to the opposite side of the player so the two colliders touch at exactly the right time to initiate walking on stairs.

    Now the player can walk past the entry point of all our stairs. But what if the stairs are the only way to progress to the left or right? What if we sometimes want them the way they were in iteration 1, forcing the player to enter if they hold left or right towards the stairs? Depending on your level’s layout, some stairs may be the only way to progress in a direction while some others merely branch away from a path that continues on as well.

    That is way, in iteration 6, we distinguish between two types of stairs entry points (“bypassable” and “non-bypassable”). To that end, let’s add a boolean called “bypassable” that we can set individually for each end of each stairs object. If an entry point is not bypassable, the bypass check collider no longer dynamically switches sides because the entry point can only be approached from one side. Instead, the collider is always fixed to the opposite side of the approaching player.

    With this addition, we now have to make sure to account for every possible button or combination of buttons the player may use to express their intent to enter the different types of stairs entry points. Up (at the bottom) and down (at the top) already work on all stairs. However, when approaching non-bypassable stairs from the left, pressing right also expresses the intent to enter the stairs (and vice versa) because moving past them is not allowed. Now when hitting a bypass check, the player will be put on the stairs only if they are currently pressing one of the buttons specific to the current case.

    If the stairs in this example are bypassable, the player must hold up to enter them, or left/right to walk past them.

    The same goes on top of bypassable stairs: down to enter them, left/right to walk past

    If they are not bypassable, holding right and/or up will both be interpreted as the player intending to enter the stairs.

    The same goes on top of non-bypassable stairs. Pressing down and/or left will result in walking downstairs.

    This should feel nice already, but depending on the exact implementation of our controls, we may have just created a different set of problems by giving the player the ability of pressing so many different buttons near the stairs to walk towards them.

    To fix them in iteration 7, we need to define which buttons cancel each other out at the entry points of the different types of stairs and which ones must not add their movement speeds up with one another. We also need to set the animations accordingly, e.g. if two buttons cancel each other out (if the player is holding left and right, for example), the player model should not walk in place. To account for all the possible problems, it is important to distinguish between ascending stairs (bottom left to top right) and descending stairs (top left to bottom right). For instance, a player standing in the range of descending stairs might cause a “walk in place” animation by holding left (away from the stairs) and down (which, remember, counts as towards the stairs in this case). Or they might hold down (towards the stairs) and right (also towards the stairs) for twice the movement speed. We have to account for all different cases of all different button combinations on all types of stairs to make sure that doesn’t cause any problems. Maybe you have a clever system that does this for you already or you’re like me and you manually define a bunch of boolean combinations outside the actual movement logic.

    Holding left, holding down, and holding left + down should all yield the same result in this situation.

    Down + right and left + right both contradict each other. Holding these combinations causes the player to stop.

    I urge you not to be lazy about this step and to cover all your bases, both to prevent bugs and make the movement as intuitive as possible.

    Alright. Getting on side stairs works fine now. Are there special rules for frontal stairs?

    We actually only need to make a few small adjustments to add frontal stairs in iteration 8, as the basic logic works the same. In our game, we made it so only a pre-defined line on frontal stairs can actually be used rather than their whole surface. As a result, they have two clearly defined entry points, just like side stairs. This also means that the player can always walk past frontal stairs, i.e. both their top and bottom are bypassable, because the width of the stairs sprite is always wider than the entry range. In some cases, of course, the player may walk past frontal stairs and bump right into a wall which then stops them anyway. The rest (about getting on the stairs) works exactly the same. Even better, we don’t ever need to account for the player trying to get up frontal stairs by pressing left or right.

    Holding up within entry range causes the player to walk to the entry point and then start walking up the stairs.

    Holding down within entry range causes the player to walk to the entry point and then start walking down the stairs.

    Part 2: Walking on the stairs

    If you’ve made it this far, you seriously deserve a pat on the back. Entering stairs is the most complicated part of this system, I promise. Now let’s define movement on the different types of stairs.

    Depending on how your game is set up (e.g. if you use actual physics and gravity), going up diagonally or vertically might cause some problems, e.g. your player slides right down on their own once they enter stairs from the top. That is why, in iteration 9, we railroad the player along the stairs. Instead of physically resting on a collider, they move along an angled line with no physics involved. In fact, let’s cancel all forces on the player object when entering stairs and ignore any forces while on the stairs. In the case of our game, we luckily don’t need to account for combat or other factors that might apply forces to the player while they’re moving on stairs.

    But how can we ensure that the player always ends up at the top or bottom when moving up and down that angle? We could calculate the angle between top and bottom, but since our steps are always the same size to fit the animation, the angle of all our side stairs is automatically always the same regardless of length, and we can simply hard-code it.

    The angle is shown in blue. It’s not a collider, but exists merely in code.

    Alright, so how do our movement controls work on stairs? Let’s design them in iteration 10. The player expects both up, right, and up + right to walk them up ascending stairs (and the opposite for descending stairs). Make sure not to add up speeds.

    We also need to once again account for button combinations that cancel each other out. For example, on ascending stairs, left and down are the same, so both should cancel out up and right, which are also the same.

    What about controls on frontal stairs? Time for iteration 11!

    You might think that pressing up or down is all there is to it. However, forcing the player to hold up or down until they’re at the very end before they can go left or right feels counterintuitive and unpolished. It may also happen that the player is still 1 pixel beneath the top or above the bottom, wondering why pressing left or right doesn’t do anything. That is why we need to define the part of the stairs within 1 meter of each end as “exit range” of the top or bottom respectively. While the player is within exit range, pressing left or right will move them towards the exit point of the stairs. Then, once they reach the exit point, they leave the stairs and continue walking in the direction they’re holding down. Maybe you’ve already noticed that this works exactly like the entry range, which allows players to use additional buttons to walk towards the entry point (which we added all the way back in iterations 3 and 4). Again, we have to account for button combinations that should cancel each other out or would add up speeds.

    Exit range at the top and bottom of stairs shown in blue. You may use a collider for this, but we calculate it from the distance between player and nearest exit point.

    While the addition of an exit range adds a lot of polish, it also brings about its own set of problems that we’ll have to solve in iteration 12. For example, what if our frontal stairs are only 2m high or even smaller, putting the player within exit range of both top and bottom at the same time? Let’s set the exit range at, say, 20% of the stairs’ total length instead of hard-coding it to 1m each.

    Shorter stairs, shorter exit range

    This fixes our problem for short stairs, but it causes a new problem for long stairs, for which 20% is too big of an exit range. So in addition to the rules we set up in iteration 12, let’s also set a maximum exit range of 1m for each exit range in iteration 13.

    Part 3: Getting off the stairs

    We’re almost done! Now that getting on and moving along stairs works, getting off the stairs is actually fairly easy.

    Right now, our player just walks right past the top and bottom into infinity:

    In iteration 14, we set up colliders that allow the player to exit the stairs (i.e. go back to regular walking controls with physics re-applied) upon being triggered. We actually re-used the range colliders of top and bottom for this:

    The only problem with that is that the colliders are triggered too early, especially at the top, because of the player collider’s height. Let’s place the colliders at the top and bottom high and low enough respectively, so the player is inside them at the very end of their walk up or down the stairs.

    Alright, but: The player is still inside that collider while they’re still in the process of entering the stairs, so can’t they turn back around and walk right through it without triggering it again, allowing them to walk past the exit point? Why, yes! In iteration 15, we solve this the same way we did for entering the stairs: We define a number of buttons that expresses the intent to leave the stairs for each case (descending/ascending/frontal), same as in iteration 6. When the player presses one of those while inside the exit range, they leave the stairs again.

    In iteration 16, we again account for low FPS which may cause the player to overshoot the exit point. If you already use FPS-independent physics calculations as mentioned in iteration 2, that’s all you need. If not, let’s teleport the player to the exact exit point, the same way we teleported them to the entry point:

    And that’s it! In only 16 simple iterations, we’ve reached the goal of creating intuitive, bug-resistant, and good-looking stairs movement.

    Of course we realize that this type of movement system isn’t at all complicated or sophisticated compared to some AI-based 3D multi-terrain movement system a programming super brain might be able to cook up nowadays. However, it works well for us and should be reasonably easy to implement into any similar 2D side-scroller. In case you’re thinking that any aspects of it could be simplified, solidified, or anything-else-ified, feel free to let us know. We’ll gladly refine both our code and this guide to better help other developers in the future.

    We hope it helped or entertained you or, at the very least, showed that a seemingly innocuous mechanic can be pretty complicated to implement sometimes.

    Julian

  • Welcome!

    Welcome!

    Hi!

    Welcome to our new blog. It will feature news, development updates, game dev tips, and the occasional unrelated opinion piece. Without further ado, let’s get to the first ever news entry!

    So what’s up?

    Here’s what’s going on besides our website overhaul: Part of our team is still working from home due to the pandemic, but it’s going smoothly so far and we haven’t missed a deadline yet. As for what we are currently working on, we’ll tell you that towards the end of the year. We have a great project in the works with a great partner, and we hope you’ll be impressed with it. In the meantime, you can enjoy some upcoming blog posts about certain mechanics, learnings etc. that won’t give too much away about the game itself. The first one coming up is a breakdown of smooth controls for movement on stairs in a 2D sidescroller.

    Become a tester

    If you can’t wait and want a chance at a sneak peek of our game, we are currently accepting some new testers for an ongoing playtest. Sign up here, and you may be contacted once the next round of tests is sent out. Sorry about the legalese; we’re just trying to cover our bases.

    Stay safe

    That’s the news for now. You’ve probably heard the phrase “in these uncertain/crazy/trying times” a lot lately. That’s because signing off with just a “See ya” currently feels out of place. People everywhere are battling a pandemic, systemic injustice, and more. We sincerely hope that, rather sooner than later, we’ll come out on the other side of this looking at massive improvements in all those areas. Until then: hang in there, stay safe, stay strong.

    Julian