So let's start from the very beginning. How do you make a rhythm game? Well..
Devlog #0 - How to Dismantle an Atomic Bomb(Breaking down the Rhythm Game)I've done rhythm games a few times, and in fact they're the only kinds of games I've seriously worked on and ever want to do. And when I first started I found there wasn't much documentation around for the general architecture of rhythm games. So, this is meant to be a
quick and dirty technical guide to how I approach my game architecture.
First, you might want to see this video for an explanation of the main mechanic of this game.
--
1. In a rhythm game, have a class that is used solely for keeping the beat.In my games I call it the Conductor.
It should have an easy function/variable that gives the song position, to be used by everything that needs to be synced to the beat. In this game for example, the Conductor has a variable called songposition which is pretty much the cornerstone of everything in the game.

The above are the variables in the Conductor class. Some are specific to my game, but the general ones that I always have are
- bpm, which gives the bpm of the song
- crotchet, which gives the time duration of a beat, calculated from the bpm
- offset, always important due to the fact that MP3s always have a teeny gap at the very beginning, no matter what you do, which is used for metadata (artist name, song name, etc)
- songposition, a variable that should be set directly from the corresponding variable on the Audio object. This varies from engine to engine, but in Unity for example, the variable to use is AudioSettings.dspTime. What I do is, in the same frame that I play the song, I record the dspTime at that moment, so then my song position variable is set on every frame as follows:
songposition = (float)(AudioSettings.dspTime – dsptimesong) * song.pitch – offset;
Aside: the song.pitch is an inbuilt variable in Unity that gives the speed the song is playing at. By incorporating it into my song position variable, I can change the playback speed and still keep everything in sync. This was used in my game to slow all the songs down 20% because I only realised after composing the music that it was too difficult.
Anyway, now that we have set up our Conductor, time to take care of the objects that need to sync to it!
--
2. Every object that needs to be in sync should do so using only the song position, and NOT anything else.This means, NO timers, NO tweens. It won’t work consistently!
If you use a timer that increments every frame (e.g. in the Update function), an inconsistent FPS is gonna throw the whole thing off.
If you use some sort of elapsed-time function, it’s still not going to be accurate enough, and if the song skips for whatever reason everything will get thrown off.
So, use only the song position. NO timers.
(Game design tip: have as many things respond to the beat as possible! Preferably everything!)
—
But even then, there is something more subtle that you need to pay attention to – and this is what I struggled with at first.
You see, even when using the song position variable for all things that sync, there still needs to be a reference point that you want to check the song position with. In the most basic case, all you would check it with would be ground zero: the start of the song.
Say for example you had four lights that you wanted to flash on the first four beats of the song. You’d write, in the Spotlight class script:
—
int beatnumber = 1; //or 2 or 3 or 4
bool islitup = false;
float bpm = 140;
float crotchet; //the duration of a crotchet
void Start(){
crotchet = 60 / bpm;
}
void Update(){
if (Conductor.songposition > crotchet * beatnumber)
islitup = true;
}
—
But other times you might want an action that happens periodically instead of only once. When implementing something like this it can be easy to have a system that makes things inaccurate without you realising it. And so the most important yet simple rule I’ve learnt from this jam is:
3. Never update your reference point arbitrarily. Only increment it.This might be a little subtle so let’s do this by example. Say you want to have a light that Flashes on every beat, instead of once. Here’s a simple way to do it which is… wrong! Can you see why?
—
float lastbeat; //this is the ‘moving reference point’
float bpm = 140;
void Start(){
lastbeat = 0;
crotchet = 60 / bpm;
}
void Update(){
if (Conductor.songposition > lastbeat + crotchet) {
Flash();
lastbeat = Conductor.songposition;
}
}
—
Literally five lines of code. Seems like it would work, right? Every time we move on to the next beat, we set the reference to the current time, and wait until another beat has passed.
BUT NO! All you will get is tears. And a flashing light that gets more and more out of sync with the music. Specifically, up to an additional 1/60th of a second more out of sync with each beat. (There’s a hint for you!)
The problem is exactly the rule I wrote above: Never update your reference point arbitrarily. Only increment it by set amounts.
When we set lastbeat to the current song position, that’s what I mean by arbitrarily updating the reference point. The problem lies in the fact that your game can only work at a specific fps. 60 frames a second, say. So you can only perform a check 60 times a second. And so by the time the if statement returns true, you have already passed the time by up to a 60th of a second. And so what you are setting the lastbeat to is not the actual last beat, but a fraction after!
IMPORTANT ILLUSTRATION:

—
So, what’s the right way to do this? By incrementing, not setting:
float lastbeat; //this is the ‘moving reference point’
float bpm = 140;
void Start(){
lastbeat = 0;
crotchet = 60 / bpm;
}
void Update(){
if (Conductor.songposition > lastbeat + crotchet) {
Flash();
lastbeat += crotchet;
}
}
—
Simple but important!
Applying These Rules To My GameTo be honest, though, I already knew this from developing my first rhythm game last year. But what caught me was a more complex manifestation of this scenario.
You see, in my game the planets orbit around each other following the speed of the song: a half revolution is exactly one beat. When the player presses a button, the orbiting planet and the stationary planet switch roles. Thus if the player presses a button every beat, the planets dance elegantly across the screen in a straight line.
The angle of the planet at any one frame would be given by the following reasoning. If song position is lastbeat at 0 degrees, and it should be lastbeat plus crotchet at 180 degrees, then the angle should be incremented by
(deltaTime / crotchet) * 180 degrees
so that by the time a crotchet had passed, we would have moved 180 degrees. Simple!
The problem is when the player doesn’t press EXACTLY on the beat (and to be fair, that’s pretty much impossible). The game had to be grid-based – it sure wouldn’t make for a very fun rhythm game if you had to compensate for a slightly early tap on one beat with a slightly late tap on the next. And so the problem I faced was snapping the planets to a grid while not making the snapping cause everything to be offset.
The first approach which at the time I thought was very clever was, at the time of the key press, to do several things.
- Record the angle difference between the moving planet’s position and where it should snap to (i.e. 180 degrees in the case of the straight line)
- Snap the moving planet to the grid, and make it the anchor
- For the planet that was previously the anchor, offset it by that angle recorded earlier, and make it the moving planet now.
One frame before key press:

One frame after:

I thought at the time it was genius – the fact that you pressed it early is now balanced out by the previous planet moving back a bit, so that the next beat would still happen at 180 degrees!
And how did this strategy work out? Terribly!
--
Everything but the Kitchen SyncAt first it seemed all in sync and dandy, but as the song progressed the syncing got worse and worse. If you’ve been reading so far, you should already know why the game slowly went out of sync.
Yep, it’s because I broke the rule of never using anything other than song position for my calculations. In this case, I compared deltaTime to the song position when updating the angle of the planets. Don’t do this!
But – and yeah it gets a little complicated – even when I replaced deltaTime with a custom timeDifference variable calculated directly from the change in song position between frames, it still didn’t work!
And here’s where the subtlety lies: by incrementing the angle each frame, I was implicitly using the song position at the previous frame as a ‘reference’. Each time I was incrementing this reference by an amount that depended on how much time had passed between frames. And the result of all these calculations, small errors built up that contributed to the game going out of sync.
(Yeah, rhythm games can be tough. In making a rhythm game, it’s absolutely vital that your engine works millisecond-precise, so don’t worry if you take a lot of time making it work perfect, it’s worth it.)
In the end, I fixed everything by going back to the golden rule: only incrementing the reference point by a set amount, an amount that did not depend on frames. Here was the very final solution, which comprised of a few sub solutions working together:
- Get rid of the incrementing angle by time difference every frame. Instead, interpolate! Record the song position at the last time the planets switched, call it last hit say. Also record the angle your planet was at, at the time of the last hit. Now, your angle at any frame is just something like this:

- To solve the player-not-hitting-exactly-on-beat problem: instead of lasthit being the time at which the key was pressed, it’s the time at which the key would have been pressed, if it was pressed exactly on time. In other words, this last hit variable is ALWAYS only incremented by multiples of the beat! This was what completely eliminated any arbitrary reference points in the calculation, just like the problem of flashing a light on every beat that was discussed earlier. In other words, we are taking away the exact time a player presses the key as a factor in our calculation, and that tidies things up a great deal.
(Exactly how to calculate the time the key would have been pressed if it was on time was a mathsy and not particularly interesting problem involving angles and geometry, but you can ask me if you're interested.)
--
ConclusionAnd that, ladies and gentlemen, is how to make a rhythm game. Hope it illuminates how seemingly small time differences are actually the most important things when developing one.
Special thanks: Tom Voros, creator of
http://microngame.com, who helped me loads along the way with hints on getting the rhythm synced in AS3.