A lot of the appeal of Bollywood Wannabe comes from the visuals. The levels are full of little details and bright colors. The characters wear a wide variety of clothes in various styles, from traditional Indian clothes to Halloween costumes. There are also surprise events happening and the choreography itself is worth admiring. However, it’s hard to appreciate it all when you are playing. The higher difficulty levels require a lot of concentration and you can’t take the time to admire the result of your amazing choreography. I needed a way to allow the player to see the result of his hard work and maybe even share it with his friends. The more I thought about it, the more I felt adding a replay was not only a good idea, but a necessary component of the game.
Replay systems in games are not a new concept. There are already a lot of very good tutorial for creating one. Please keep in mind that this article is not an in-deep tutorial on how to create a replay system. The goal of this article is simply to give an overview of the process and share my own experience on the matter.
Where to start
After a quick search, I discovered there was mostly three way of creating a replay: recording a video in real time, recording the information about every object in the game and recording only the user inputs. Each technique has its own advantage and drawback.
Recording a video
This technique is simple to understand. A picture of the screen is regularly captured and saved in memory, along with the music and sounds, to create a video. If the user wants to watch the replay, you simply load the video data and play it.
- You can fast forward, skip or rewind at will.
- The replay is always perfect.
- Since video data is usually compressed, the result may not be as pretty as the original.
- Makes the game a lot slower since the video needs to be recorded in real time and recording is very resource intensive.
- Use a lot of memory to store the video.
- Slow to load when you want to see the replay.
This technique is also very simple. You save all the information available for each object in the level (position, rotation, scale, state of its animation, etc.) and all the information needed by the game (current score, state of the UI, etc.) for each frame. If the user wants to watch the replay you load the data and use it to place all the objects in the level.
- The replay is always perfect.
- You can fast forward, skip or rewind at will.
- Makes the game a little slower since information about every object needs to be saved for each frame.
- Might be a bit slow when you want to see the replay since the information about each object need to be updated for each frame.
- A lot of code must be created to save and load all the information required.
- Use a lot of memory to store all the information needed (though probably less than the video).
Recording only user inputs
For each frame you save the user actions, if any. A user action generally consists of pressing or releasing a button. The saved user actions are then used to simulate a playthrough.
This technique only works when the game’s code is determined. A deterministic program will give the exact same result if the exact same inputs are provided. This is harder to achieve than it may seems.
- Do not affect the speed of the game since very little information is saved.
- The replay is as fast as the original since the same code is used.
- Doesn’t need a lot of memory since very little information is saved.
- Very little code must be created.
- The code can be reused for multiplayer functions.
- A small bug may create a big difference between the original play and the replay.
- You cannot skip or rewind. Fast forward is also limited.
Let’s do this
I decided to go with the last option. To make sure that the game’s replays were perfect, I created a special test mode. In this mode, I recorded the state of every object in the level including the position of every character. Those values would be compared with the one produced during the replay and if a difference was found, no matter how small, a warning was shown.
With this tool, I could now start testing the replay and make sure that every part of my code worked perfectly. Two part of the game needed attention, the rhythm management and the level.
Originally, the rhythm bar was synchronized with the music. This meant that the beats would follow the music perfectly. This created a problem in the replay since the music was played in real time, but the rest of the game followed the timing of the original playthrough. A small difference in timing meant that the replay could show a beat was missed when the original was on time. This was fixed by forcing the beats to follow the same time as the replay. However, this sometime caused the music to be out of sync with the rest of the replay. To avoid this problem we forced the game to synchronize with the replay even if it had to create false lag in the process.
This was certainly the most problematic part. The objects interactions in a level are mostly controlled by a physic engine, Box2D. To be able to create a perfect replay, the engine itself needs to be deterministic. After a quick search through the documentation and the forum, it seemed like everyone agreed that it was.
But in order to achieve a perfect simulation, you need to execute the exact same events in the same order. This wasn’t a problem until I added an intro to every level. To avoid annoying the player, the intro can be skipped. Skipping the intro meant that a lot of steps weren’t executed anymore by the engine. Even if I made sure every object was exactly in the same position and state as it would be after executing the intro, the simulation wasn’t exactly the same anymore. I had no choice but to execute the intro every time. To keep the skipping option, I had to execute the intro in a fast forward way and hide the result behind a fade.
Adding some surprises
From the start, I wanted each play session to be a bit different. I wanted the player to be surprised by what was happening on the screen. To do that, I had to avoid having the exact same character entering the screen and positioning itself in the exact same way every time. The game needed a little bit of randomness, but the replay still needed to be identical to the original. The solution to this problem is easy for anyone who knows how randomness works in computer programs. The truth is that true randomness can’t exist in a program. The most common way to generate a “random” number is to apply a mathematic formula to a pre-existing number, the seed, and use the result as the seed for the next random number needed. For a complete explanation of how this works, you can check the Wikipedia article about random number generator. There are also lots of tutorials on how to create custom random functions and, alternatively, one can simply set the seed of the standard C++ random function. Now all I had to do was save the seed in the replay data and use it in the replay.
I didn’t face a lot of problems developing Bollywood Wannabe replay system, but those I experienced were hard to fixes. The biggest problem with relying on a determinist system to manage a replay is that the smallest difference can send the replay in a completely different direction. I once tested a replay only to see my character stuck behind a wall, trying to walk and dance like the obstacle didn’t exists and failing a level I had previously completed. In this case, I discovered that some of the moving objects in the level used a different clock then the rest of the game. Using the same clock solved this problem. However, as obvious as the problems were once I found them, debugging the replay was often an incredibly long process. Sometimes the difference was so small between the original and the replay that I couldn’t reproduce it half the time. The replay would produce an error once, I would redo the level to test it again and everything was now working flawlessly. The code I used to debug the replay could detect difference so small that they often didn’t cause any visible problem. It’s often tempting, after hours of trying to find the source of that tiny difference, to just stop everything and decide this is “good enough”. In the end though, I was able to fix all the problems and the replay worked fine.
Creating a functional replay system is a lot or work, mostly because of the testing involved in making sure the replay is identical to the original playthough. However, one of the hidden benefits of this testing is that it forced me to thoroughly debug the game. A lot of small bugs that would probably have been overlooked were found because the replay system magnified them. It also gave me a good tool to test the events and visuals in the game.
Of course, the biggest benefit is allowing the player to watch just how well he played that level. I think I played very well, don’t you think?