Last year, I decided to enter Rhythm Jam 2024. The goal was to create a game with the theme Divergence.
I decided my game would be a version of Dance Dance Revolution (DDR). To apply the theme, I wanted to have the arrows not only move up the screen, but also swap positions horizontally to really confuse the player.
To find music I could use without being concerned with licensing, I went to Free Music Archive where I was able to find some good music tracks to use for free in this project.
Making the arrows appear on screen and move towards the top was simple. One thing I wasn't sure how to tackle was timing the arrows to the music. I first tried creating a timer that was synced to the beats per second (BPS) of the chosen song. This seemed limiting, as it didn't easily support adding additional arrows or complex sequences for different points of the song. Additionally, if the song ever had a pause or the BPS would change, then the arrows would no longer be in sync with the beat.
The second method I tried was using Audacity, a free audio editing tool I used a lot in the past. Audacity allows you to create labels at different times in the track. These labels can then be exported as a text file. I decided I would create labels in the track with L,R,U,D to decide what arrows should display at that point. To show 2 or more arrows, I could write L,U,D for example to show a left, up and down arrow all at once for that beat.
In addition to displaying the arrow direction, I realised I could also use these labels to decide when my arrows should be power chords (e.g. hold down for more points) and could even dictate when to swap their x positions.
The exported .txt file was formatted showing the timestamps and arrow types:
32.409268 32.409268 d
33.309087 33.309087 u
33.790569 33.790569 l,r
Here is a snippet showing how I read in the exported Audacity text file and decide what arrows to display.
public class SongSequencer {
String[] songLines;
int runCount = 0;
GameScreen gameScreen;
boolean allArrowsFinished = false;
public void start(final GameScreen gameScreen) {
this.gameScreen = gameScreen;
Song song = gameScreen.getSong();
songLines = readSongLines(song);
}
public void update(float songProgress) {
if(allArrowsFinished) return;
String beatString = songLines[runCount];
if(beatString.contains("M:")) {
String[] moveStrings = beatString.split(",");
for(String moveString: moveStrings) {
try {
float xPos = Float.parseFloat(moveString.substring(moveString.indexOf(":") + 1));
if (moveString.contains("l")) {
arrowSlots.get(0).movePlaces(xPos);
} else if (moveString.contains("d")) {
arrowSlots.get(1).movePlaces(xPos);
} else if (moveString.contains("u")) {
arrowSlots.get(2).movePlaces(xPos);
} else if (moveString.contains("r")) {
arrowSlots.get(3).movePlaces(xPos);
}
} catch (Exception ignored) {
}
}
runCount++;
return;
}
float nextArrowPos = Float.parseFloat(beatString.substring(0, 8));
float arrowDelay = song.delay;
float addTime = nextArrowPos - arrowDelay;
if(songProgress >= addTime) {
String arrowDirectionString = beatString.substring(beatString.lastIndexOf("\t")+1);
String[] arrowDirections = arrowDirectionString.split(",");
for(String arrowDirection : arrowDirections) {
gameScreen.addArrow(arrowDirection);
}
runCount++;
if(runCount >= songLines.length) {
allArrowsFinished = true;
}
}
}
public String[] readSongLines(Song song) {
FileHandle file = Gdx.files.internal("songs/" + song.fileName + "/beat_track.txt");
if (!file.exists()) {
return null;
}
return file.readString().split("\\r?\\n");
}
public boolean isAllArrowsFinished() {
return allArrowsFinished;
}
}
After this, the rest fell into place. I chose a stock video to show in the background and added the score counter to show points and combo count. Then I play-tested to make sure each song was actually playable.
Overall this was a fun project to tackle and I learned a bit about the challenges with making these types of games. I got sick of listening to the same music over and over though.