Rhythm Quest Devlog 19 — Extra Stage Unlocks

Song Previews

This is the kind of engineering task that is pretty nontrivial yet will probably never show up on any sort of programming interview or anything… In my head this is something that could easily snowball into a pile of weird edge conditions if not handled robustly. In other words, this is the exact sort of thing that gamedev programming is made of…

  • You can press any of the preview buttons, at any time (except during transitions)
  • The music preview should loop, with a fade at the beginning/end
  • The menu music should fade in/out as the music preview starts/stops
  • Pressing the same preview button again should stop the music
  • Pressing a different button should stop that song and start the new one
  • The preview should automatically stop if you exit that menu
enum AudioState {
Stopped,
FadingIn
Playing,
FadingOut,
}
AudioSource _audioSource;AudioState _currentState = AudioState.Stopped;string _currentClip = "";
void PreviewButtonClicked(string clipName) {
if (_currentClip != clipName) {
// We need to transition to the new clip...

// Start loading the new clip asynchronously (?)
AsyncOperationHandle _asyncHandle = Addressables.LoadAssetAsync(clipName);
if (_currentState == AudioState.Playing) {
// Start fading out the current clip...
_currentState = AudioState.FadingOut;
BeginFadeAudioSource(_audioSource, 0.0f);
// Maybe use a coroutine to wait until the volume is 0??
// What if the preview button gets pressed again in the meantime??
...
} else if (_currentState == AudioState.FadingOut) {
...
}
}
...
}
AudioSource _audioSource;string _targetClip = "";AsyncOperationHandle _asyncHandle;// The button click handler changes _targetClip, but does nothing else.
void PreviewButtonClicked(string clipName) {
if (_targetClip == clipName) {
// Stop playback.
_targetClip = "";
} else {
_targetClip = clipName;
}
}
// Frame-based logic determines at each frame what needs to be done
// with the audio source to match _targetClip.
void Update() {
if (_targetClip == "") {
// We don't want to play anything. Fade volume towards 0.
_audioSource.volume = Mathf.MoveTowards(_audioSource.volume, 0.0f, Time.unscaledDeltaTime);
} else if (_audioSource.clip.name != clipName) {
// We're playing the wrong clip. Fade volume toward 0.
_audioSource.volume = Mathf.MoveTowards(_audioSource.volume, 0.0f, Time.unscaledDeltaTime);

// Start loading the new clip, if we haven't already.
if (!_asyncHandle.IsValid()) {
_asyncHandle = Addressables.LoadAssetAsync(_targetClip);
}

if (_audioSource.volume == 0.0f && _asyncHandle.IsDone) {
// New clip is loaded and fading is done.
// Replace the clip and play.
_audioSource.clip = _asyncHandle.Result;
_audioSource.volume = 1.0f;
_audioSource.Play();

// (This is a struct, not a class)
_asyncHandle = new AsyncOperationHandle();
}
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
DDRKirby(ISQ)

DDRKirby(ISQ)

9-bit chiptune artist and indie game developer — http://ddrkirby.com/