DEADBEAT Devlog #02: Start of Production (How to Rhythm in UE4)


Project Type: DEADBEAT Development
Engine: Unreal Engine 4.23.1
Language: C++

In this Devlog we put our focus once again on handling beats. We will discuss the hurdles we encountered to implement a solid on-beat event system with the tools provided by Unreal Engine, whilst staying strictly limited to C++.
We will go over this in quite some detail, since there are almost no resources online on how to make a Rhythm based system for UE4 in specific.

Issues with 4.24.x

First of all, we would like to discuss the reason for which we switched to 4.23.1 for the official development of DEADBEAT.
The developers of our team had minimal issues in 4.24.x , and didn't notice any significant differences, however, the artist side of the DEADBEAT team noticed several issues regarding crashes when working with meshes and lighting. 
To ensure a flawless development phase, as well as minimal crashes, we decided to go back to an older version of Unreal Engine, being 4.23.1.

This decision was made after being informed by several Artists outside of the DEADBEAT team of the risk of using this version at this moment and because of experience from the team itself.

Implementing a Rhythm system/Beat Handler

Implementing rhythm in Unreal Engine 4 was not an easy task. The documentation regarding this topic is scarce and so is any information specific to Unreal Engine. In our endeavors of searching for any guidance towards starting with a proper implementation, we encountered that most people make a Rhythm game using Unity. In the end the basis of the system we use is also part of a Unity source.

There were several attempts made but each with their own flaws in our experience:

  • TimeSynth Plugin
    • PRO: Automatic Callback functions bind-able with Delegates.
    • CON: Changing Speed/Pitch of the song in Runtime is a hassle and is undocumented + Beta Plugin.


  • Sound Cue's
    • PRO: Easily used, no plugins required, can easily change speed and pitch.
    • CON: Very Latency potent, needs specific method to avoid frame dependency.
Frame Dependency?

An important thing to know before we start going into detail about this Beat Handler, lets take a moment to talk about frame dependent rhythm.
This was a huge problem in the prototyping phase. Based on the framerate, the system could automatically fall out of sync. This also happened when speeding up the music.

So what do you use instead of Timers,..

Song Position.
Lets take a look at the variables we used for this system:

m_IsMusicPlaying => Enabled when music starts. 
m_BarCount => Keeps track of the current bar.
m_BeatCount => Keeps tracks of the current beat.
m_SongPos => Checks the current song position. 
m_InputTime => Used for input system (to check if player jumps on beat).
m_AudioStartTime => Start time when audio started playing.
m_Crotchet (Quarter Note) => Calculation to get the time in MS in between quarter notes.
m_LastBeat => Time of the last beat registered
m_PitchMultiplier => Used for speeding up the song and Crotchet speed.

  1.  Record the current time with UGameplayStatics::GetAudioTimeSeconds when you start the music. Store this in AudioStartTime.
  2.  Every TICK:
    1.  Check if music is playing.
    2. Store the SongPosition = (GetAudioTimeSeconds - m_AudioStartTime).
    3. Check if the position of the song is bigger than the last registered beat + a quarter note. This means we encountered the next beat. (m_SongPos > m_LastBeat + m_Crotchet)
    4. Add Crotchet to LastBeat
    5. Increase beatcount and barcount (if necessary).

The power of this system is that it's completely based around the song position, rather than a frame dependent system, like the DEADBEAT Prototype used.

We can't say enough how helpful this little tutorial was from Ludum Dare.
A lot of props to the Ludum Dare team for going in clear depths regarding this topic!

Increasing the speed/pitch

Another strength of using this system was the ease to changing pitch and speed and corelating this to the BPM/Crotchet calculation.

  • Every beat, Increase the PitchMultiplier variable with a set amount.
  • Set this value in the audiocue (AudioCue::PitchMultiplier = ..)
  • Recalculate the Crotchet. This is quite an easy formula:
    m_Crotchet = 60/m_Bpm * (1/m_PitchMultiplier);

Combining this system with the increasing speed and pitch gave us the following result, which was very close (except for the music) to what we need as a strong basis for DEADBEAT. Check out the video to see what it turned out like so far!
https://streamable.com/yoyvi


Handling on-beat input

As an extra for this week,  we also implemented a very solid basis for to check if a player jumps on the beat. In the future of DEADBEAT this will be a mechanic that rewards players for a movement on the beat.

Implementing this on top of the system we just created was a breeze, and after some fine-tuning it was already a really solid base. The basic idea is:

  • Call a function from your player to the BeatHandler class.
  • This function registers an input time, which is the current AudioTimeSeconds -  m_AudioStartTime.
  • Have a member variable that keeps an input offset.
  • Check if the input time is in range of the beat + the offset and the beat - offset.
  • If this is true, a movement on the beat happened!


And that's it for this week!

Hopefully you've enjoyed reading this somewhat tutorial structured Devlog, and we hope to see you next week to read about our second week of production!

The DEADBEAT Team.

Get DEADBEAT

Leave a comment

Log in with itch.io to leave a comment.