{"author_link":"\/users\/rucksack","author_name":"rucksack","author_uid":"rucksack","comments":[],"epoch":1777227521,"event":"LD49","format":"md","ldjam_node_id":269572,"likes":12,"metadata":{"p_key":"160022","p_author":"rucksack","p_authorkey":"1216445","p_urlkey":"382390","p_title":"How to Make a Rhythm Game - Lessons Learned","p_cat":"LDJam ","p_event":"LD49","p_time":"1777227521","p_likes":"12","p_comments":"0","p_status":"WAYBACK","us_key":"1216445","us_name":"rucksack","us_username":"rucksack","event_start":"1633046400","event_key":"119","event_name":"Ludum Dare 49"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD49","removed_author":false},"_superparent":258323,"_trust":2,"author":216445,"body":"For this Ludum Dare, we made a rhythm game that's heavily inspired by Crypt of the Necrodancer. It was quite a journey to figure out how to make everything follow the rhythm. But now, almost a week after the end of the jam and after fixing some nasty bugs, I finally feel like I understand what I was actually doing last weekend, and I want to share what I've learned in this little \"How to Make a Rhythm Game\".\n![Cableman_GIF_cars.gif](\/\/\/raw\/d7d\/43\/z\/72c53.gif)\n\n\n\n# Get the beat\nI'm a programmer, not a musician, so I can't tell you how to make a beat, only how to get the beat into the game. Ask your favourite musician to give you a file with the time of every beat in the song (along with the song itself). One way to get file like that is to export the \"beat\" track as MIDI and then convert it to a JSON file using one of the many free online converters. Then you can load this file into your game engine. The beat of our first song is 90bpm and has a simple 4\/4 rhythm, it looks like this in the code:\n```\nbeat_array[0]=0\nbeat_array[1]=0.67\nbeat_array[2]=1.33\nbeat_array[3]=2\nbeat_array[4]=2.67\nbeat_array[5]=3.33\nbeat_array[6]=4\n```\nThis approach lets you be super flexible with any beat your musician throws at you. Even if they make some weird polyrhythmic noise, you could easily import it into the game (playing that would be a whole different story though). Here's an example of the slightly more complex beat of our second song:\n```\nbeat_array[0]=0\nbeat_array[1]=0.67\nbeat_array[2]=1.33\nbeat_array[3]=2\nbeat_array[4]=2.33\nbeat_array[5]=2.67\nbeat_array[6]=3.33\n```\nIn the code, you can then compare the current position in the song with the beat array to see if a beat has occured and how long it is before the next one.\n```\n     beat                     beat\n-----0.67s--------------------1.33s-----\n      |                        |\n      |                        |\n```\n\n\n# Move to the beat\nIn our rhythm game, the player has to give input to the rhythm of the beat. If they manage to hit a beat, they'll move in a direction, but if they don't, they'll lose health. The issue here is, what exactly does it mean to \"hit\" a beat?\n\nThe player will never input at exactly the same time as the beat because they're an imprecise human and the game logic runs in frames, so it'll never detect the beat at exactly the right time. So, you need some kind of buffer time that allows for delayed input:\n\n```\n     beat                     beat\n-----0.67s--------------------1.33s-----\n      |                        |\n      |                        |\n------+------            ------+------\ninput allowed            input allowed\n      |                        |\n      |                        |\n```\nThe key thing to consider is that this buffer time has to be applied in both directions. The player might hit a key slightly *after* or *before* a beat happens. To take this into account, we do the following each time the player gives input:\n- check if the time difference to the previous *or* next beat is smaller than the buffer time\n- if yes, check if the beat has already been hit\n- if not, mark the beat as hit\n- move the player\n\nNow the player can move to the beat, and with the buffer time you also have an easy tool to scale difficulty.\n\n\n# Get hit by the beat\nThe next thing to do is to get the environment, or enemies, to move to the beat as well, and make them damage the player if they collide. Movement is pretty straightforward: every time a beat occurs, all enemies move. The nice thing about this is that now the whole world can potentially vibe to the beat.\n```\n     beat                      beat\n-----0.67s---------------------1.33s-----\n      |                         |\n      |                         |\n------+------             ------+------\ninput allowed             input allowed\n      |                         |\n      x                         x\nenemies moving            enemies moving\n      |                         |\n      |                         |\n```\nBut now it get's a bit trickier. How can you check for a collision with the player? If you do a basic collision check every frame, you're basically undoing the buffer time we added earlier. If the player wanted to move out of the way of an enemy, they would need to move away from it slightly *before* the beat actually happens. Likewise, if a player wanted to move to a tile where there's an enemy who'll move away next beat, they would have to move slightly *after* the beat happens. Ultimately, it would make the whole game feel unfair and broken.\n\nYou can solve this problem by doing the \"movement\" and \"damaging\" of enemies at different times. For example, you can check for collision once exactly between two beats. So, the enemies are moving to the beat, but damaging to the \"off-beat\".\n```\n     beat                      beat\n-----0.67s---------------------1.33s-----\n      |            :            |\n      |            :            |\n------+------      :      ------+------\ninput allowed      :      input allowed\n      |            :            |\n      x            :            x\nenemies moving     :      enemies moving\n      |            x            |\n      |     enemies damaging    |\n```\nThis might *look* a bit weird, because you can sometimes see the player \"glitching\" through enemies during the buffer time, but it actually *plays* way better than with enemies that move and damage at the same time.\n![Cableman_Buffer frames ohne Anmerkung.gif](\/\/\/raw\/d7d\/43\/z\/72c48.gif)\nYou could also check for collisions right after the buffer time is over, but this can cause a problem that I didn't think about during the game jam:  What happens when the beats are really close together or the difficulty is so low, that the buffer times of two beats overlap?\n\n```\n     beat              beat\n-----2.00s-------------2.33s-----\n      |                 |\n      |                 |\n------+-----------------+--------\ninput allowed     input allowed\n      |                 |\n      x                 x\nenemies moving    enemies moving\n      |                 |\n\n```\nIf you've just set it up so that the enemies are doing damage at the end of the buffer time, you're now hurting the player during the buffer time of the next beat. This might not sound that serious, but it can actually break the game if you remove too much of the buffer time for the next beat. Just play the jam version of our game on the \"basically cheating\" difficulty. The enemies are damaging so late, it can feel like they have a hitbox lagging behind them. It can make the game a lot harder, because you have to hit every beat just so slightly too late.\n\nSo, the most important thing I've learned from making our rhythm game is that **you can't just focus on one beat. You've always got to check the timing between the last and next beat.**\n\nIf you want to try our game, and give us some feedback on how we implemented the rhythm or anything else, you can do so here: https:\/\/ldjam.com\/events\/ludum-dare\/59\/cableman-a-rhythm-game\n","comments":2,"comments-timestamp":"2026-04-28T15:47:04Z","created":"2021-10-03T20:11:51Z","files":[],"files-timestamp":0,"id":269572,"love":12,"love-timestamp":"2026-05-04T19:51:57Z","meta":[],"modified":"2026-05-04T19:51:57Z","name":"How to Make a Rhythm Game - Lessons Learned","node-timestamp":"2026-04-26T18:18:41Z","parent":263558,"parents":[1,5,9,258323,263558],"path":"\/events\/ludum-dare\/49\/unstable-the-unstable-cattle-stable\/how-to-make-a-rhythm-game-lessons-learned","published":"2026-04-26T18:18:41Z","scope":"public","slug":"how-to-make-a-rhythm-game-lessons-learned","subsubtype":"","subtype":"","type":"post","version":1372680},"node_metadata":{"n_key":"269572","n_urlkey":"382390","n_parent":"263558","n_path":"\/events\/ludum-dare\/49\/unstable-the-unstable-cattle-stable\/how-to-make-a-rhythm-game-lessons-learned","n_slug":"how-to-make-a-rhythm-game-lesson","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"216445","n_created":"1633291911","n_modified":"1777924317","n_version":"1372680","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/49\/unstable-the-unstable-cattle-stable\/how-to-make-a-rhythm-game-lessons-learned","text":"For this Ludum Dare, we made a rhythm game that's heavily inspired by Crypt of the Necrodancer. It was quite a journey to figure out how to make everything follow the rhythm. But now, almost a week after the end of the jam and after fixing some nasty bugs, I finally feel like I understand what I was actually doing last weekend, and I want to share what I've learned in this little \"How to Make a Rhythm Game\".\n![Cableman_GIF_cars.gif](\/\/\/raw\/d7d\/43\/z\/72c53.gif)\n\n\n\n# Get the beat\nI'm a programmer, not a musician, so I can't tell you how to make a beat, only how to get the beat into the game. Ask your favourite musician to give you a file with the time of every beat in the song (along with the song itself). One way to get file like that is to export the \"beat\" track as MIDI and then convert it to a JSON file using one of the many free online converters. Then you can load this file into your game engine. The beat of our first song is 90bpm and has a simple 4\/4 rhythm, it looks like this in the code:\n```\nbeat_array[0]=0\nbeat_array[1]=0.67\nbeat_array[2]=1.33\nbeat_array[3]=2\nbeat_array[4]=2.67\nbeat_array[5]=3.33\nbeat_array[6]=4\n```\nThis approach lets you be super flexible with any beat your musician throws at you. Even if they make some weird polyrhythmic noise, you could easily import it into the game (playing that would be a whole different story though). Here's an example of the slightly more complex beat of our second song:\n```\nbeat_array[0]=0\nbeat_array[1]=0.67\nbeat_array[2]=1.33\nbeat_array[3]=2\nbeat_array[4]=2.33\nbeat_array[5]=2.67\nbeat_array[6]=3.33\n```\nIn the code, you can then compare the current position in the song with the beat array to see if a beat has occured and how long it is before the next one.\n```\n     beat                     beat\n-----0.67s--------------------1.33s-----\n      |                        |\n      |                        |\n```\n\n\n# Move to the beat\nIn our rhythm game, the player has to give input to the rhythm of the beat. If they manage to hit a beat, they'll move in a direction, but if they don't, they'll lose health. The issue here is, what exactly does it mean to \"hit\" a beat?\n\nThe player will never input at exactly the same time as the beat because they're an imprecise human and the game logic runs in frames, so it'll never detect the beat at exactly the right time. So, you need some kind of buffer time that allows for delayed input:\n\n```\n     beat                     beat\n-----0.67s--------------------1.33s-----\n      |                        |\n      |                        |\n------+------            ------+------\ninput allowed            input allowed\n      |                        |\n      |                        |\n```\nThe key thing to consider is that this buffer time has to be applied in both directions. The player might hit a key slightly *after* or *before* a beat happens. To take this into account, we do the following each time the player gives input:\n- check if the time difference to the previous *or* next beat is smaller than the buffer time\n- if yes, check if the beat has already been hit\n- if not, mark the beat as hit\n- move the player\n\nNow the player can move to the beat, and with the buffer time you also have an easy tool to scale difficulty.\n\n\n# Get hit by the beat\nThe next thing to do is to get the environment, or enemies, to move to the beat as well, and make them damage the player if they collide. Movement is pretty straightforward: every time a beat occurs, all enemies move. The nice thing about this is that now the whole world can potentially vibe to the beat.\n```\n     beat                      beat\n-----0.67s---------------------1.33s-----\n      |                         |\n      |                         |\n------+------             ------+------\ninput allowed             input allowed\n      |                         |\n      x                         x\nenemies moving            enemies moving\n      |                         |\n      |                         |\n```\nBut now it get's a bit trickier. How can you check for a collision with the player? If you do a basic collision check every frame, you're basically undoing the buffer time we added earlier. If the player wanted to move out of the way of an enemy, they would need to move away from it slightly *before* the beat actually happens. Likewise, if a player wanted to move to a tile where there's an enemy who'll move away next beat, they would have to move slightly *after* the beat happens. Ultimately, it would make the whole game feel unfair and broken.\n\nYou can solve this problem by doing the \"movement\" and \"damaging\" of enemies at different times. For example, you can check for collision once exactly between two beats. So, the enemies are moving to the beat, but damaging to the \"off-beat\".\n```\n     beat                      beat\n-----0.67s---------------------1.33s-----\n      |            :            |\n      |            :            |\n------+------      :      ------+------\ninput allowed      :      input allowed\n      |            :            |\n      x            :            x\nenemies moving     :      enemies moving\n      |            x            |\n      |     enemies damaging    |\n```\nThis might *look* a bit weird, because you can sometimes see the player \"glitching\" through enemies during the buffer time, but it actually *plays* way better than with enemies that move and damage at the same time.\n![Cableman_Buffer frames ohne Anmerkung.gif](\/\/\/raw\/d7d\/43\/z\/72c48.gif)\nYou could also check for collisions right after the buffer time is over, but this can cause a problem that I didn't think about during the game jam:  What happens when the beats are really close together or the difficulty is so low, that the buffer times of two beats overlap?\n\n```\n     beat              beat\n-----2.00s-------------2.33s-----\n      |                 |\n      |                 |\n------+-----------------+--------\ninput allowed     input allowed\n      |                 |\n      x                 x\nenemies moving    enemies moving\n      |                 |\n\n```\nIf you've just set it up so that the enemies are doing damage at the end of the buffer time, you're now hurting the player during the buffer time of the next beat. This might not sound that serious, but it can actually break the game if you remove too much of the buffer time for the next beat. Just play the jam version of our game on the \"basically cheating\" difficulty. The enemies are damaging so late, it can feel like they have a hitbox lagging behind them. It can make the game a lot harder, because you have to hit every beat just so slightly too late.\n\nSo, the most important thing I've learned from making our rhythm game is that **you can't just focus on one beat. You've always got to check the timing between the last and next beat.**\n\nIf you want to try our game, and give us some feedback on how we implemented the rhythm or anything else, you can do so here: https:\/\/ldjam.com\/events\/ludum-dare\/59\/cableman-a-rhythm-game\n","title":"How to Make a Rhythm Game - Lessons Learned","wayback_source":[]}