【進捗報告】予定は埋まるもの【2023年6月 その1】
はじめに
こんにちは。のんびりです。
最近は雨も多く、寒暖差激しい梅雨入りでてんてこまいの日々が続いていますね。
しかし、のんびりのやる気は依然最高気温をキープし続けています!
このままいくと夏には大変すごいことになりますねハハハ….
というわけで今回の作業のお話に進みましょう
今回の目標
サウンド再生機能の実装
はい、こちらをもりもり進めていました。
Unity の Audio 機能は比較的優秀で、ただ音を再生させたいだけならすぐに実行可能です。GameObject に Audio Source コンポーネントを付けて必要な音を設定して Play するだけ。簡単ですね
しかし、「再生したBGMを停止したい」「曲をクロスフェードしたい」「再生中のSEの種類を知りたい」「シーンをまたいだ曲の再生がしたい」等、サウンドを丁寧に管理したい場合はちゃんとしたマネージャーを用意する必要があります
今回はそのはしりとして、簡単に SoundManager を用意してみました。
そして今回はちょっとコードを公開してみようと思います。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
namespace SoundManager
{
[System.Serializable]
public class Sound
{
public enum Type
{
BGM,
SE,
TERM
}
public string name;
public AudioClip clip;
[Range(0f, 1f)]
public float volume = 1f;
[Range(0.1f, 3f)]
public float pitch = 1f;
public bool loop = false;
public Type type = Type.SE;
[HideInInspector]
public AudioSource source;
}
public class SoundManager : SingletonMonoBehaviour<SoundManager>
{
/// シーン読み込み時に必ず呼ばれる
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnLoad()
{
GameObject app = GameObject.Instantiate(Resources.Load<GameObject>("SoundManager"));
DontDestroyOnLoad(app);
app.name = "SoundManager";
}
public AudioMixerGroup MixerGroup { get; private set; } = null;
public Sound PlayingBGM { get; private set; } = null;
public float FadeDuration { get; set; } = 1.0f;
// サウンドのBGMとSEを指定するための配列
public Sound[] _soundList;
#region MonoBehaviour の実装
void Start()
{
// サウンドの初期化処理
foreach (var sound in _soundList)
{
sound.source = gameObject.AddComponent<AudioSource>();
sound.source.clip = sound.clip;
sound.source.volume = sound.volume;
sound.source.pitch = sound.pitch;
sound.source.loop = sound.loop;
sound.source.outputAudioMixerGroup = MixerGroup;
}
}
#endregion
#region public API
/// <summary>
/// SE の再生処理
/// </summary>
/// <param name="name"></param>
public void PlaySE(string name)
{
Sound sound = FindSound(name);
// 見つからなければ終了
if (sound == null)
{
Debug.LogWarning("Sound not found: " + name);
return;
}
// SE じゃなければ終了
if (sound.type is not Sound.Type.SE)
{
Debug.LogWarning("This Sound is not SE" + name);
return;
}
sound.source.Play();
}
/// <summary>
/// BGM の再生処理
/// </summary>
/// <param name="name"></param>
public void PlayBGM(string name, bool isFade = true)
{
Sound sound = FindSound(name);
// 見つからなければ終了
if (sound == null)
{
Debug.LogWarning("Sound not found: " + name);
return;
}
// BGM じゃなければ終了
if (sound.type is not Sound.Type.BGM)
{
Debug.LogWarning("This Sound is not SE" + name);
return;
}
if(PlayingBGM is not null)
{
StartCoroutine(FadeOut(PlayingBGM));
StartCoroutine(FadeIn(sound));
}
else
{
StartCoroutine(FadeIn(sound));
}
}
/// <summary>
/// サウンドの停止処理
/// </summary>
/// <param name="name"></param>
public void StopSound(string name)
{
Sound sound = FindSound(name);
if (sound == null)
{
Debug.LogWarning("Sound not found: " + name);
return;
}
sound.source.Stop();
}
/// <summary>
/// すべての BGM をストップする
/// </summary>
public void StopBGM()
{
foreach(var sound in _soundList)
{
if(sound.type == Sound.Type.BGM)
{
sound.source.Stop();
}
}
}
#endregion
#region private API
// サウンドを検索する処理
Sound FindSound(string name)
{
return System.Array.Find(_soundList, sound => sound.name == name);
}
// BGM のフェードアウト
IEnumerator FadeOut(Sound sound)
{
if(sound.source.isPlaying is false) { yield break; }
float startVolume = sound.source.volume;
float time = 0.0f;
while (time < FadeDuration)
{
time += Time.deltaTime;
sound.source.volume = Mathf.Lerp(startVolume, 0.0f, time / FadeDuration);
yield return null;
}
sound.source.Stop();
}
// BGM のフェードイン
IEnumerator FadeIn(Sound sound)
{
sound.source.Play();
float time = 0.0f;
float startVolume = 0.0f;
PlayingBGM = sound;
while (time < FadeDuration)
{
time += Time.deltaTime;
sound.source.volume = Mathf.Lerp(startVolume, 1.0f, time / FadeDuration);
yield return null;
}
}
#endregion
}
}
SoundManager という、このコンポーネントをアタッチした Prefab の用意が事前に必要になりますが、比較的シンプルに出来たと思います。
実装した機能としては以下のようになっています
BGM, SE の再生
曲の一斉停止
BGMのクロスフェード
ひとまず、「必要最低限の機能」を用意しました。これから必要に応じてこのクラスを整備していきます。
「必要に応じて」というのは自分の中で結構重要な意味を持っています。
もっと事前に用意できる機能は多々あると思われますが、それを用意していると時間もかかるし、まだ使わない機能の実装は「最後まで使わない可能性がある」と考えています。
必要な時に必要な実装をして、落ち着いたタイミングで機能をまとめて共通化する、という流れが自分に合っている気がします。
どうしても気になる場合は「今後必要になりそうな機能」としてメモを残しておき、時間の空いた時に実装するようにしています。
ひとまず、これで最低限の SoundManager が用意できました
次回の目標
次回の目標はこちらです
作業も佳境になってきましたが、完成を見越して動き始めています。
同か温かい目で見守ってやってください…!!
【ポエム】予定は埋まるもの
今年に入ってから、土日が暇でやることがない、みたいなことが全くなくて作業の時間が取れていないんですよね…
主に遊びに行ったりオフ会に行ったりすることが多いのですが、「あそびを理由に開発が進まない」とは絶対に言いたくないんですよね!
というのも、仮に土日ともフリーな週があったとして、ず~~っと集中し続ける事ができるかといわれると、そうではありません。できても5,6時間がせいぜいです。
となると、平日の時間や、スキマ時間などを使って作業を進めないと結局進捗が生まれていかないのが事実なのです。
何が言いたいかというと、「予定は埋まるもの」として考え、常に先んじて行動することが大事だなと、最近になって感じてきました。
自分は基本ギリギリにならないとできない人間でしたが、ゲームで食べていくと決めた以上、時間を有効に使って人生を歩んでいきたいです!