حلقة الموسيقى الخلفية مع مقدمة

تحديث الصفحة :
تاريخ إنشاء الصفحة :

بيئة التحقق

نوافذ
  • ويندوز ١١
محرر الوحدة
  • 2021.3.3f1
حزمة نظام الإدخال
  • 1.3.0

المتطلبات الأساسية لهذه النصيحة

تم إجراء الإعدادات التالية مسبقا كمقدمة لوصف هذه النصيحة.

حول المواد المدرجة مع العينة

يتم استعارة BGM من الموقع التالي.

حول الملف الصوتي للحلقة مع مقدمة

هذه المرة ، ستقوم وظيفة Unity القياسية فقط بتشغيل حلقة مع مقدمة ، ولكن هذا غير مدعوم كوظيفة قياسية. في المقام الأول ، لم يتم إصلاح مواصفات الحلقة مع المقدمة كملف صوتي ، لذلك تختلف طريقة الإنشاء اعتمادا على إطار اللعبة.

هذه المرة ، سنقوم بإعداد ملفين صوتيين لجزء "المقدمة" وجزء "الحلقة" ، وتشغيل جزء المقدمة مرة واحدة ، وتشغيل جزء الحلقة بشكل متكرر. لذلك ، يرجى إعداد الملفين أعلاه كملفات صوتية.

تعتبر بعض مواقع التوزيع هذا وتوزع الملفات الصوتية بشكل منفصل مسبقا. إذا لم يكن الأمر كذلك ، فأنت بحاجة إلى جعله بنفسك باستخدام أداة تحرير الصوت.

إذا كنت تريد تقسيم الملف إلى قسمين ، فإننا نوصي بالتنسيق التالي لملف الصوت.

  • أوغفوربيس (.ogg)
  • الرعايا (.wav)

لمزيد من المعلومات، راجع وثائق الوحدة الرسمية.

حول تشغيل موسيقى الخلفية المتكررة باستخدام المقدمة

قم بإنشاء واجهة مستخدم تسمح لك بتشغيل موسيقى الخلفية وإيقافها مؤقتا وإيقافها كعينة. له نفس التصميم مثل عينة تشغيل الموسيقى الخلفية العادية.

أضف ملفين صوتيين إلى مشروعك ، مقسما إلى جزأين ، مقدمة وحلقة.

هذه المرة ، سننشئ برنامجنا الخاص لتشغيل 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 مسبقا ، عند بدء اللعب ، قم بتشغيل المقدمة أولا. بالنسبة للحلقات ، قم بجدولتها لتشغيلها عند انتهاء المقدمة. بمجرد بدء تشغيل الحلقة ، قم بتشغيلها بشكل متكرر.

في الأصل ، بالنسبة للحلقات ، كان يكفي تعيين الخاصية true على ، loop لكنها لم تعمل بشكل صحيح في 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 من المفتش وإضافة إجراءات لكل زر.

البرنامج النصي مرفق بنظام الحدث. نظرا لأنك تحتاج إلى تعيين Intro Loop Audio ، فقم بتعيين EventSystem الذي يحتوي على Intro Loop Audio.

الآن قم بتعيين طرق لأحداث النقر للأزرار الثلاثة.

بمجرد إعداد كل شيء ، قم بتشغيل اللعبة وحاول تشغيلها. عندما ينتهي التشغيل حتى النهاية ، يمكنك أن ترى أنه يتكرر من منتصف الأغنية ويتم تشغيله. بالطبع ، يفترض هذا أن البيانات الصوتية مقسمة بدقة وأن الحلقات متصلة بدقة.

عرض المدة الحالية

كمكافأة من هنا ، أضفنا خاصية تسمح لنا بالحصول على وقت التشغيل الحالي في ،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}";
  }
}

قم بإرفاق برنامج نصي بالنص وقم بتعيين EventSystem باستخدام IntroLoopAudio.

قم بتشغيله ومعرفة ما إذا كان يتم عرض وقت التشغيل الحالي. أيضا ، عند التكرار ، حاول التحقق مما إذا كان الوقت يعود إلى نقطة الحلقة.