인트로가 있는 루프 배경 음악

페이지 업데이트 :
페이지 생성 날짜 :

검증 환경

윈도우
  • 윈도우 11
Unity 에디터
  • 2021.3.3f1
입력 시스템 패키지
  • 1.3.0

이 팁의 전제 조건

이 팁에 대한 설명의 전제로 다음 설정이 미리 이루어졌습니다.

샘플에 포함 된 재료에 대해

BGM은 다음 사이트에서 차용하고 있습니다.

인트로가 있는 루프의 오디오 파일에 대해

이번에는 표준 Unity 함수만 인트로와 함께 루프를 재생하지만, 표준 함수로는 지원하지 않습니다. 애초에 인트로를 오디오 파일로 하는 루프의 사양이 정해져 있지 않기 때문에, 게임 프레임워크에 따라 생성 방법이 다릅니다.

이번에는 "인트로"파트와 "루프"파트에 2 개의 오디오 파일을 준비하여 인트로 파트를 한 번 연주하고 루프 파트를 반복 재생합니다. 따라서 위의 두 파일을 오디오 파일로 준비하십시오.

일부 배포 사이트에서는이를 고려하여 오디오 파일을 별도로 미리 배포합니다. 그렇지 않은 경우 음성 편집 도구를 사용하여 직접 만들어야 합니다.

파일을 둘로 분할하려면 다음 형식의 오디오 파일을 사용하는 것이 좋습니다.

  • 오그보비스 (.ogg)
  • WAV (.wav)

자세한 내용은 공식 Unity 문서를 참조하십시오.

인트로가 있는 반복되는 배경 음악 재생에 관하여

배경 음악을 재생, 일시 중지 및 중지할 수 있는 UI를 샘플로 만듭니다. 일반 배경 음악 재생 샘플과 동일한 레이아웃을 가지고 있습니다.

프로젝트에 두 개의 오디오 파일을 추가하고 인트로와 루프의 두 부분으로 나눕니다.

이번에는 인트로로 반복되는 BGM을 재생하는 자체 프로그램을 만듭니다. 스크립트를 만들고 이름을 IntroLoopAudio .

스크립트는 다음과 같습니다. 다음 사이트의 코드를 참조하고 있지만 WebGL과 잘 작동하지 않고 좀 더 제어하고 싶었 기 때문에 많은 코드를 추가했습니다.

[참고] 【유니티】인트로 + 루프 플레이 구현 - 7080 + 1

using UnityEngine;

/// <summary>
/// イントロ付きループ BGM を制御するクラスです。
/// </summary>
/// <remarks>
/// WebGL では PlayScheduled で再生するとループしないのでその対応を入れている。
/// WebGL では2つの AudioSource を交互に再生。
/// </remarks>
public class IntroLoopAudio : MonoBehaviour
{
  /// <summary>BGM のイントロ部分の音声データ。</summary>
  [SerializeField] private AudioClip AudioClipIntro;

  /// <summary>BGM のループ部分の音声データ。</summary>
  [SerializeField] private AudioClip AudioClipLoop;

  /// <summary>BGM のイントロ部分の AudioSource。</summary>
  private AudioSource _introAudioSource;

  /// <summary>BGM のループ部分の AudioSource。</summary>
  private AudioSource[] _loopAudioSources = new AudioSource[2];

  /// <summary>一時停止中かどうか。</summary>
  private bool _isPause;

  /// <summary>現在の再生するループ部分のインデックス。</summary>
  private int _nowPlayIndex = 0;

  /// <summary>ループ部分に使用する AudioSource の数。</summary>
  private int _loopSourceCount = 0;

  /// <summary>再生中であるかどうか。一時停止、非アクティブの場合は false を返す。</summary>
  private bool IsPlaying
    => (_introAudioSource.isPlaying || _introAudioSource.time > 0)
      || (_loopAudioSources[0].isPlaying || _loopAudioSources[0].time > 0)
      || (_loopAudioSources[1] != null && (_loopAudioSources[1].isPlaying || _loopAudioSources[1].time > 0));

  /// <summary>現在アクティブで再生しているループ側の AudioSource。</summary>
  private AudioSource LoopAudioSourceActive
    => _loopAudioSources[1] != null && _loopAudioSources[1].time > 0 ? _loopAudioSources[1] : _loopAudioSources[0];

  /// <summary>現在の再生時間 (s)。</summary>
  public float time
    => _introAudioSource == null ? 0
      : _introAudioSource.time > 0 ? _introAudioSource.time
      : LoopAudioSourceActive.time > 0 ? AudioClipIntro.length + LoopAudioSourceActive.time
      : 0;


  void Start()
  {
    _loopSourceCount = 2;   // WebGL でなければ 1 でもよい

    // AudioSource を自身に追加
    _introAudioSource = gameObject.AddComponent<AudioSource>();
    _loopAudioSources[0] = gameObject.AddComponent<AudioSource>();
    if (_loopSourceCount >= 2)
    {
      _loopAudioSources[1] = gameObject.AddComponent<AudioSource>();
    }

    _introAudioSource.clip = AudioClipIntro;
    _introAudioSource.loop = false;
    _introAudioSource.playOnAwake = false;

    _loopAudioSources[0].clip = AudioClipLoop;
    _loopAudioSources[0].loop = _loopSourceCount == 1;
    _loopAudioSources[0].playOnAwake = false;
    if (_loopAudioSources[1] != null)
    {
      _loopAudioSources[1].clip = AudioClipLoop;
      _loopAudioSources[1].loop = false;
      _loopAudioSources[1].playOnAwake = false;
    }
  }

  void Update()
  {
    // WebGL のためのループ切り替え処理
    if (_loopSourceCount >= 2)
    {
      // 終了する1秒前から次の再生のスケジュールを登録する
      if (_nowPlayIndex == 0 && _loopAudioSources[0].time >= AudioClipLoop.length - 1)
      {
        _loopAudioSources[1].PlayScheduled(AudioSettings.dspTime + (AudioClipLoop.length - _loopAudioSources[0].time));
        _nowPlayIndex = 1;
      }
      else if (_nowPlayIndex == 1 && _loopAudioSources[1].time >= AudioClipLoop.length - 1)
      {
        _loopAudioSources[0].PlayScheduled(AudioSettings.dspTime + (AudioClipLoop.length - _loopAudioSources[1].time));
        _nowPlayIndex = 0;
      }
    }
  }

  public void Play()
  {
    // クリップが設定されていない場合は何もしない
    if (_introAudioSource == null || _loopAudioSources == null) return;

    // Pause 中は isPlaying は false
    // 標準機能だけでは一時停止中か判別不可能
    if (_isPause)
    {
      _introAudioSource.UnPause();
      if (_introAudioSource.isPlaying)
      {
        // イントロ中ならループ開始時間を残り時間で再設定
        _loopAudioSources[0].Stop();
        _loopAudioSources[0].PlayScheduled(AudioSettings.dspTime + AudioClipIntro.length - _introAudioSource.time);
      }
      else
      {
        if (_loopSourceCount >= 2)
        {
          // WebGL の場合は切り替え処理を実行
          if (_loopAudioSources[0].time > 0)
          {
            _loopAudioSources[0].UnPause();
            if (_loopAudioSources[0].time >= AudioClipLoop.length - 1)
            {
              _loopAudioSources[1].Stop();
              _loopAudioSources[1].PlayScheduled(AudioSettings.dspTime + (AudioClipLoop.length - _loopAudioSources[0].time));
              _nowPlayIndex = 1;
            }
          }
          else
          {
            _loopAudioSources[1].UnPause();
            if (_loopAudioSources[1].time >= AudioClipLoop.length - 1)
            {
              _loopAudioSources[0].Stop();
              _loopAudioSources[0].PlayScheduled(AudioSettings.dspTime + (AudioClipLoop.length - _loopAudioSources[0].time));
              _nowPlayIndex = 0;
            }
          }
        }
        else
        {
          // WebGL 以外は UnPause するだけ
          _loopAudioSources[0].UnPause();
        }
      }
    }
    else if (IsPlaying == false)
    {
      // 最初から再生
      Stop();
      _introAudioSource.Play();

      // イントロの時間が経過した後に再生できるようにする
      // 設定する時間はゲーム刑か時間での設定となる
      _loopAudioSources[0].PlayScheduled(AudioSettings.dspTime + AudioClipIntro.length);
    }

    _isPause = false;
  }

  /// <summary>BGM を一時停止します。</summary>
  public void Pause()
  {
    if (_introAudioSource == null || _loopAudioSources == null) return;

    _introAudioSource.Pause();
    _loopAudioSources[0].Pause();
    if (_loopAudioSources[1] != null) _loopAudioSources[1].Pause();

    _isPause = true;
  }

  /// <summary>BGM を停止します。</summary>
  public void Stop()
  {
    if (_introAudioSource == null || _loopAudioSources == null) return;

    _introAudioSource.Stop();
    _loopAudioSources[0].Stop();
    if (_loopAudioSources[1] != null) _loopAudioSources[1].Stop();

    _isPause = false;
  }
}

코드가 길기 때문에 자세한 내용은 생략하겠지만 인트로와 루프 AudioClip 는 미리 설정해 두고, 연주를 시작할 때 인트로를 먼저 재생하십시오. For 루프는 인트로가 끝날 때 재생되도록 예약합니다. 루프 재생이 시작되면 반복해서 재생합니다.

원래는 loop for 루프에서 프로퍼티를 true 로 설정하면 충분했지만 WebGL에서는 제대로 작동하지 않았습니다. AudioSource 두 개의 루프를 준비하고 번갈아 가며 재생합니다. WebGL을 고려할 필요가 없다면 코드를 반으로 줄일 수 있습니다.

스크립트를 만든 후 객체에 연결합니다. 보통은 빈 오브젝트를 만들어서 붙이는 것이 좋을지도 모르지만 번거롭기 때문에 EventSystem에 붙이는 것이 좋습니다.

인트로 및 루프 항목이 있으므로 오디오 파일을 각각 드롭하고 설정합니다.

이 시점에서는 특별한 처리가 필요하지 않습니다. 버튼을 클릭 할 때 처리하고 싶기 때문에 버튼에 대한 스크립트 (ButtonEvent)를 만듭니다.

스크립트는 다음과 같습니다.

using UnityEngine;

public class ButtonEvent : MonoBehaviour
{
  [SerializeField] private IntroLoopAudio IntroLoopAudio;

  public void OnClickPlay()
  {
    IntroLoopAudio.Play();
  }
  public void OnClickPause()
  {
    IntroLoopAudio.Pause();
  }
  public void OnClickStop()
  {
    IntroLoopAudio.Stop();
  }
}

IntroLoopAudio 인스펙터에서 각 버튼에 대한 액션을 추가합니다.

스크립트는 EventSystem에 연결됩니다. 인트로 루프 오디오를 설정해야 하므로 인트로 루프 오디오가 있는 EventSystem을 설정합니다.

이제 세 버튼의 클릭 이벤트에 메서드를 할당합니다.

모든 것이 설정되면 게임을 실행하고 플레이해 보십시오. 재생이 끝나고 나면 노래 중간부터 반복되어 재생되는 것을 볼 수 있습니다. 물론 이것은 오디오 데이터가 깔끔하게 분할되고 루프가 깔끔하게 연결되어 있다고 가정합니다.

현재 지속 시간 표시

여기에서IntroLoopAudio 보너스로 현재 재생 시간을 가져올 수 있는 속성을 추가했으므로 표시해 보겠습니다.

먼저 시간을 표시할 텍스트를 배치합니다.

스크립트를 만듭니다.

using UnityEngine;
using UnityEngine;
using UnityEngine.UI;

public class TextEvent : MonoBehaviour
{
  [SerializeField] private IntroLoopAudio IntroLoopAudio;

  private Text _text;

  // Start is called before the first frame update
  void Start()
  {
    _text = GetComponent<Text>();
  }

  // Update is called once per frame
  void Update()
  {
    _text.text = $"AudioPlayTime : {IntroLoopAudio.time}";
  }
}

텍스트에 스크립트를 첨부하고 IntroLoopAudio를 사용하여 EventSystem을 설정합니다.

그것을 실행하고 현재 재생 시간이 표시되는지 확인하십시오. 또한 루핑할 때 시간이 루프 포인트로 돌아가는지 확인하십시오.