Echo Rift
- Roberto Reynoso
- May 17
- 13 min read
Updated: Aug 7
Engine: Unity Engine | Genre: Action, Rhythm | Platform: Steam, Nintendo Switch | Timeframe: 8 months+
Role: Game Engineer & Design Lead
PLAY THE GAME ON STEAM
Overview
The game is played on a 9 x 6 grid where the player has to move to the beat of the music according to the BPM that is used. Keeping the beat is extremely important, since you will more than likely be punished for not doing so. The player will have to defeat the enemy that is in front of them while keeping the rhythm.
The game draws inspiration from many different titles, which include Crypt of the Necrodancer, One Step to Eden, Megaman Battle Network, and more.
Echo Rift was an endeavor of hard work. I am so proud of what our team was able to create, despite the challenges we faced. We were able to overcome the design challenges we had early on in the project through our communication and hard work. I decided to go through my contributions and experience for what I did for the project in each phase of development. I won't be able to hit everything, but I will go through the bulk of it.
Team
Production - Steven Pasinsky & Preston Phung & Akash Singh
Engineering - Roberto Reynoso & Chai Lin & Jacob Liddle & Alice Ssu-Fan Wang & Yan Liu & Yunxiao Cai
Design - Roberto Reynoso & Chai Lin & Steven Pasinsky & Sarah Homer & Adam Treu & Yuxiao Zhou & Shuochen Sun & Minyuan Yang
Art - Briton Flynn & Heejae Lim & Anastasiia Murza & Paul Ruppar & Murinus Liu & Ziyi Zhu
Tech Art - Xinyi Qu & Zhexuan Zou & Po-Jui Huang & Doris Hu
Music - Timmy Reynolds & Kevin West & Will Aleshire
User Research - Arjun Shivakumar
Prototype Phase
I'll try to keep this short and sweet, since I would prefer to talk about the other phases of development. While this was important, the ideas and systems here were not used for the actual game itself. Rather, it was one of the few prototypes that was worked on in order to explore different possibilities of what the game could be. (Feel free to skip this section if you would like to just get into the development of the main game.)
During the prototyping phase, we split into smaller teams to explore various directions and find what felt fun. My team, which included Alice, David, and Briton, was drawn to a musical action-adventure concept inspired by The Legend of Zelda and Death's Door. We imagined players using instruments to solve puzzles and interact with the environment.
We settled on four instruments: Guitar, Keyboard, Drums, and Vocals. Each had unique uses. The Keyboard had an early-phase ability that let players pass through tagged objects, later replaced by a freeze ability that stopped moving objects to enable puzzle-solving, like freezing platforms or locking pieces in place. The Keyboard also had a grapple ability that allowed the player to get across specific gaps. The implementation used involved a grapple manager that kept track of various edge cases, but if I were to do that again, I would have used a state machine of some sort. These systems relied on interactable classes with virtual overrides for how each instrument affected specific objects.
The Vocal instrument featured a pitch mechanic where players had to match a directional threshold using the joystick to shrink or enlarge objects. It also had a Construction ability used to summon bridges into place.
The Guitar had a laser ability that bounced off reflective objects, players could rotate these to solve reflection-based puzzles. It also had a follow mechanic, letting players lead fans (NPCs) to specific spots using the interactable system.
The Drum had a break ability that destroyed nearby objects with a shockwave that were tagged accordingly. Below you can see what this prototype looked like when showcased.
Pre-Production Phase
During the pre-production phase, we were trying to figure out what the game should be and what the vision should look like. With that being said, my teammate Chai and I created a design document outlining what could realistically work in terms of scope. We wanted to combine the overworld elements of the game with separate combat mechanics, specifically the grid based combat shown in the trailer at the top of this write-up.
The initial concept for the overworld was similar to what you might see in games like Paper Mario, where there are active elements such as puzzles, explorable locations, and enemies visible in real time that the player can interact with. Combat would be initiated by the player choosing to make contact with an enemy, which would then trigger the grid based combat.
Through further discussion with the team, we decided that including both fully realized combat and an overworld with meaningful content might be too much to handle within scope.
We decided to move toward building a roguelike game with various abilities and items that the player could bring with them into combat. I was then tasked with building a map generation system for the nodes and branching paths that the player could take. This would encourage replayability, allowing the player to go through the game more than once.
I built a system where the designer could adjust the number of branches and nodes in each section, along with the weights for each node type. This gave them the ability to experiment with what felt cohesive, rather than having the layout feel completely random. I also accounted for the possibility of applying specific static rules to the system if the designers wanted certain conditions to remain fixed.
Alongside this, I created a Player Progression Controller, which had separate functionality from the one used during combat. It referenced the node the player was currently on, and that node contained information about the possible branching paths the player could take. The player would then choose one of the available branches, and the system would move and rotate the player accordingly along the path to the next node.
Alpha Phase
During the Alpha phase, I began adding rules to the map generation, including a rule that prevented crossing paths while still guaranteeing a valid path to the end. This was a bit of a challenge since the sections were customizable in size, which required many checks to ensure that custom sizes worked correctly without creating crossing paths. Realistically limiting what sizes were allowed and how branches could generate into the next section was key to avoiding overlap.
As a team, we decided that making the game a roguelike might be out of scope, so we made one of our final pivots in terms of progression. We chose to implement a simple level-to-level system in the overworld, which was more manageable within our scope. Since the game was combat-centric, this approach ended up fitting well.
During this time, we struggled a bit to find our footing, and I was open to doing whatever I could to support the team in any way related to design, so we could end up with a working and fun product by the end of development. Through careful decision-making, I was given the opportunity to become design lead for the project, allowing me to help guide the team toward completing a full game. It wasn't easy juggling both gameplay engineering and design, but I was determined to do everything I could to assist the team and create something awesome.
Moving on to attacks, I was tasked with creating the Aerial Ace Attack. This attack incorporates the grid manager and pattern creation so that the attack can be placed in the correct spot. The attack is separated into different states: before hit, on hit, and after hit. These states rely on a couple of event calls, including OnFullBeat and OnNewBeatWindowEntered, which are managed by the Beat Manager. We can subscribe to these events to ensure that the action executes each state correctly, in time with the beat. This includes playing the animations and triggering the visual effects associated with the attack.
Once the action reaches the on-hit check, it will determine whether or not the player should take damage. After the action completes, we unsubscribe from the events and release the object back into the pool by setting it to inactive. This does not delete the object; it simply deactivates it until we need to use it again.
This required using the grid system we had in place. It is a 9 x 6 grid, and each beat has a 9 x 6 2D texture placed on top of it. The reason for doing it this way was to use the pixel data to determine where an action should spawn on the grid. This also made it easy to create a variety of patterns that we could lay over the grid, which would then trigger the appropriate actions at the designated locations according to the pattern the grid manager executed.

Here we use a program called Aseprite to create our patterns. The way our system works is that a sequence of patterns is loaded in. As you can see in the image, there are 10 in total, and each one is applied to the grid, correlated with a specified action that should be spawned in alongside the pattern. We then apply the sequence to the beat of the current stage the player is on. It plays one pattern per beat, although padding can also be added. Any pixels that are not black will activate an action at the specified spot on the grid in-game.
Each action was originally coordinated by color value, so the grid manager could determine which action to activate at each position. However, this was later changed. Now, the system only checks if the pixel is not black. If so, it knows that an action should be applied there. How does it know which action to apply? The pattern itself now stores that information, so the grid manager no longer needs to decide which action to trigger.
This system also makes use of object pooling. Since constantly instantiating new actions would be expensive, we define how many of each action should exist and pull from that pool whenever a specific action needs to be used.
This was what we showcased before entering the beta phase. Most of the systems were in place and ready for content to be added. Here we see what the core game loop would become: all attacks (patterns) performed by the enemy would play in sync with the beat of the music.
We took inspiration from Crypt of the NecroDancer, Megaman Battle Network, and One Step From Eden when designing the combat. We had to consider how much would be too much for the player to manage, since they would need to both attack the enemy and avoid the patterns on the grid at the same time.
This was not the final look by any means. We later refined timing, the UI, and beat indicators to better support the player in managing everything. I believe we ended up with something pretty solid.
The showcase might look a bit rough around the edges, but it was a major milestone in defining what Echo Rift would become. During this process, I was tasked with making sure everything was ready for the presentation. This included assembling all components of our MVP (Minimal Viable Product), ensuring they worked together, fixing bugs, and writing any additional code needed to pull it all together.
Beta Phase
During the beta phase, we needed to bring all of the pieces together and end up with a complete game. Working with the other leads and designers, we determined what was necessary to reach that goal while setting aside some stretch goals in case we had extra time. One of these stretch goals was to add a third area, but due to time constraints, we had to settle on two areas. Each area included three encounters: two minor enemies and one boss.
I was tasked with adding the ability to target a specific action at the player. The question was, how do we take a pattern that we create and target the player during runtime? The solution I came up with was to center the pattern on whatever tile the player is currently standing on. This required iterating through the pattern so we could build the targeted version around the player's current location, which would act as the center point on the grid.
There were edge cases to account for. For example, what if we had a pattern with a width of 2? The grid we were working with is 9 x 6, so if the player was standing in the center, would we place the pattern to the left or right of them? We could always choose one side, but we decided to randomize it so the player would have to react more to the beat instead of memorizing fixed placements.
If the player was standing on the edge of the grid, we would simply apply the pattern one tile to the right or left, depending on space. For any given pattern size, we had to adjust where the center point landed on the grid. Of course, if a pattern was extremely large, that could cause problems. In terms of design, we just avoided creating overly large patterns.
Another possible solution, instead of shifting the center point, was to cut off any parts of the pattern that extended beyond the bounds of the grid. Interestingly, textures will wrap around by default, so part of a pattern could appear on both the left and right sides of the grid. To fix this, we ignored any pixels that were out of bounds rather than applying them. This behavior was formalized as one of the rules of targeting, though we still kept the pattern sizes reasonable during design to avoid issues.
As I had stated before, we pivoted from using random map generation to a static overworld, where the player would travel to each node linearly rather than having the option to branch off. This change definitely had its benefits. First, it kept us within our scope, and second, it gave us the opportunity to design a world that felt more beautiful and cohesive. With procedural map generation, that can be harder to achieve, especially if the player expects the world to look different depending on how nodes branch.
To implement this change, I had to rework the logic behind how nodes functioned and how the player moved between them. However, this approach didn’t work well with our goals, so I eventually opted to use a spline system instead. This allowed me to easily design a path that the player could follow from node to node in the world.
I developed the tail drum attack for Boss 2, which would lift the player if they were standing on a lifted tile. This required the action to disable the player's ability to move while being lifted. There was an edge case where the player could move on the frame just before getting lifted, which caused some janky movement when the player returned to the grid.
The solution to this issue was to ensure the player could only be hit by the last tile (they were on) that was lifted, and they would be returned to that specific tile afterward.
I later added more functionality to this attack. We decided to invert the player's movement controls for a number of steps, dictated by a counter stored on the player. The player would also turn green during this effect. The only way to remove the inverted controls was to move to a valid tile, which would decrease the counter.
I had to alternate between shaders applied to the character whenever their controls were inverted, so the player would have visual feedback that the inversion was active.
Gold Phase
Finally, we had a working game, though we still needed to put all of the finishing touches together.
The Echo Rift intro introduces the player to the world they will be experiencing, and the main menu serves as the player's means to start a new game or continue their adventure. We used Unity's PlayerPrefs system to store save data, allowing the player to return to where they left off. Settings were also implemented to allow changes to control, audio, and display preferences.
I have to give kudos to all the artists who worked hard to make the world look beautiful and cohesive. It turned out great. Here, you can see my spline system in action. I created this system because it made it much easier to design the path we wanted Echo, the main character, to follow.
Here you can check out what the finished product looked like at this point. This is from the initial version of the game. After release, we made a handful of updates for bug fixes, new features, and quality-of-life improvements.
Release Phase
After seeing feedback on our game, I realized it was quite difficult, even for the average player who is used to rhythm-based games.
I decided to add difficulty settings to the game so that newer players could have an easier time getting through it, while more experienced players could increase the difficulty if they wanted to. Adding this feature involved more than just adjusting the player's max health. I had to ensure that PlayerPrefs also stored the selected difficulty, allowing the player to continue on the same setting across sessions.
In addition to saving the difficulty, I made UI changes to the main menu so that the player could choose their difficulty when starting a new game. I also added the ability to change the difficulty on the fly while the player was in the overworld.
I then integrated the score system with each difficulty setting, so that the player's score would be tracked and displayed based on their chosen difficulty. This also required using PlayerPrefs to store the user's scores per difficulty, allowing them to see how they performed at each level under different difficulties.
Here you can see a few updates in the overworld, such as the difficulty being displayed at the top of the level title, along with the scores associated with that difficulty. You can also see that the node asset was changed to something that fit much better, and a completion flag now appears next to the node after the player finishes a level. Additionally, you can see how stage selection works if you want to go back and replay a level.
We decided that a quality-of-life change we could make was to show the player visually where they would teleport when using their teleport ability.
I wanted to show what the head drum attack does in contrast to the tail drum attack. The head drum has a hitbox that will damage the player if they are hit by the body of the snake, and the rest of the tiles lift up, giving the player inverted controls for one count. (The player must move to a valid tile to reduce the invert counter by one.)
The tail drum ability is similar, but it always hits the centermost part of the grid, with the hitbox aligned along the drumstick and tiles lifting up in a wave-like pattern. This gives the player five inversion counters instead of one, since it is easier to dodge.
Both the tail drum and head drum attacks required me to work with Unity's animation system. I had to ensure the animations were timed to the beat of the music, and I used animation keys to activate and deactivate the hitboxes at the correct times.
I implemented the cutscenes for each enemy in the game. This required me to work with the animator and trigger specific animations at specific times. I built a waypoint system that allowed the player to move to the appropriate destination during a cutscene. In the case above, it would be under the stairs of the manor.
I also created a dissolve material that was applied after using the rockify shader, which transformed the enemy into stone. Once the explosion effect played, the dissolve shader was then applied to the enemy.
I went through the same process with DJ Cyberbyte, as you can see here.

I worked extensively with the Steam SDK, as I was responsible for pushing many of the updates to the platform. I also implemented many of the achievements you see here, along with the necessary logic required to unlock them.
One of the final changes I made was reworking the lighting in the game's treasure room.
Reflection
Echo Rift is the most ambitious project I’ve worked on to date and easily one of the most fulfilling. Throughout development, I wore multiple hats, from gameplay engineering to design and more.
I'm especially proud of the adaptability and problem-solving our team demonstrated during development. We iterated, pivoted, and constantly refined our vision until we delivered something we could be proud of. The experience has definitely made me a stronger and more confident game developer.
Comments