新しいフォルダー(2)と「4時やんけ」ボイスでUnityを喋らせて開発QOLを爆上げヒャッホイする方法
この記事は、もなふわすい~とる~む Advent Calendar 2020の9日目の投稿です。
まとめ:
巻乃もなかはイイぞ
Unityで制作を行っている時、仕事だろうが個人作業だろうが基本締め切りに追われますよね?(断定)
そして締め切りに追われると余裕が無くなり、余裕が無くなるとミスをしやすい負のスパイラルに陥りやすい orz
そんな時、Unityが推しの声で喋りかけてきたら嬉しくないですか??
削られたMPを維持出来たりするかも知れないですよね???
そんな夢を叶えてくれるUnityのエディター拡張があります!
Voiceerです!
更に何と、このVoiceerはデフォルトでサンプルボイスがついてくるのですが、これ用にボイス提供をしてくださっているバーチャルタレントさんがいます!
そう、我らが巻乃もなかさんです!!マジ天使!!!
・・・とまあ、謎の怪しい勧誘っぽい出だしで始めましたが、Voiceerに限らず巻乃さんが提供してくださってるボイスをUnityに入れて開発QOLを爆上げして行く方法を模索しよう!という内容になります。
Voiceer
まずVoiceerを入れてみましょう。上記VoiceerのリリースページからUnityPackageをダウンロードし
Unityプロジェクトに入れてください
次に上記巻乃さんのボイスをダウンロードし、Unityに入れましょう
入れたら今度はVoiceerで使う設定を作ってみましょう。
メニュー<Windows<Voiceer<Voiceer Preset Generator
を開いてください
データ設定用ウィンドウが出てきます
これでまず新しい設定ファイルを作りましょう。
VoicePresetの新規作成の項目を生めて【新規作成】ボタンを押しましょう
そうすると新しく設定データが作成され、今度の設定画面が表示されます
この一覧の「コンパイルが終了した時」がUnity内にあるイベントになります。その横にある【+】ボタンを押すとそのイベント時に再生するボイスを割り当てられます
おつもな~
これでスクリプト編集をするなどをしてコンパイルが走った際にはこのボイスが再生されます。
一つのイベントに複数ボイスを入れると、入れたボイス一覧の中からランダムに再生されます
こんな感じにすると、この4つの中からランダムで再生されます。
あ、はい、テストして・・・ます・・・多分・・・きっと・・・未来の自分は頑張ります・・・はい、すみません・・・
も、もうすぐ・・・終わります・・・明日イベント? が、頑張ります・・・
設定が完了したら
メニュー<Windows<Voiceer<Voiceer Preset Selector
を開いてください
ウィンドウが出てきますので、先ほど作成して設定を入れたSciprtableObjectをドラッグ&ドロップしてください
【*】スクショの画像は自分がVoiceerを別のフォルダに入れてしまった後にパスを修正しないという怠けをした結果変な感じになってますが気にしないでください。直さないのはサボりです。気が向いたらやります
これでUnityが喋りはじめます!ひゃっほい!!
おつもな~
これでことある毎に推しの癒しボイスが聞けてUnity開発QOLが爆上げヒャッホイ!!デス!!!!
Voiceerのデータ共有
無事Unityが喋るようになりましたが、やはり推しは布教したいですよね(断定)。という事でVoiceerには作成したデータセットをパッケージ化する仕組みが用意されています!
メニュー<Windows<Voiceer<Voiceer Preset Generator
を開いて、共有したいデータを設定してください。
そうすると先ほどのボイス設定画面がまた出てきます
これで一番下にある【パッケージを出力する】ボタンを押してください
そうするとUnityPackageファイルが作成されますので、それを共有すれば僕の考えたさいつよのUnityを共有できます!
ただ、共有する時は【ボイスの権利や利用規約】等はちゃんと守りましょう!
Voiceerを自分のエディター拡張に組み込む
Unityが定期的に喋りはじめましたが、たまに何か寂しいと思っていたら自分で作ったエディター拡張は喋らないじゃ~ね~か!喋ってくれよ、もなふわすい~とる~む!!
という事で、自分で作ったエディター拡張にVoiceerの再生処理を入れ込んでみようと思います。
何か適当に昔公開したnamespaceを入れるだけの簡単なお仕事をする拡張に入れて見ます
【*】多分上がってる物と違うコードなのであくまで一例として見て貰えればと
上記のStartボタンを押されたらnamespaceを入れ込むこんな感じの処理があるとします
private void DrawStartButton ()
{
// ボタン表示
if (GUILayout.Button ("Start", GUILayout.Height (30f)))
{
// ボタンが押されたので処理を行う
Debug.Log ("Start! namespace = " + m_namespaceName);
// ファイル一覧を作る
List<string> fileList = CreateFileList ();
int count = fileList.Count;
if (count <= 0)
{
Debug.Log ("ファイルがありませんでした");
return;
}
// デバッグ用にファイル一覧を書き出す
for (int i = 0; i < count; ++i)
{
Debug.Log ("File = " + fileList[i]);
}
// namespace入れ込む
NamespaceInserter insterter = new NamespaceInserter ();
insterter.InsertNamespace (m_namespaceName, fileList, m_isOverwrite);
// ファイル更新
AssetDatabase.Refresh ();
// 終了&ダイアログ表示
Debug.Log ("Finished inserting!");
EditorUtility.DisplayDialog (WINDOW_NAME, ON_COMPLETE_MESSAGE, "OK");
}
}
ボタンを押した時にボイス再生を入れ込めたらきっと嬉しいですよね。という事でVoiceerのボイスを再生する方法を探してみましょう。
取り合えずVoiceerのスクリプトファイル一覧を眺めて見るとします
お、「SoundPlayer」っていかにも音を再生してくれそうなやつがいますね!ちょっと開いて見ましょう
ほう、こいつはstaticクラス。つまり、ずっと生きているし、どこからでもアクセス出来そうだぞ・・・!
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Voiceer
{
public static class SoundPlayer
{
...色々(ry
そしてこの中にPlaySoundといかにも音を鳴らしてくれそうなメソッドがあるぞ!しかもこいつpublicかつstatic、つまりどこからでも使えちゃいそうだぜ!
public static class SoundPlayer
{
private static VoicePreset CurrentVoicePreset => VoiceerEditorUtility.GetStorageSelector()?.CurrentVoicePreset;
public static void PlaySound(Hook hook)
{
//VoicePresetがあるか
if (CurrentVoicePreset == null)
{
return;
}
という事はボタンを押したらコイツを呼び出せば良いのでは?という事でやってみよう。SoundPlayerのクラスにnamespace Voiceerと確かついていた気がするので、恐らくVoiceer.SoundPlayerと書いてアクセス出来るのだろう
Voiceer.SoundPlayer.PlaySound (...引数がある?);
お、このメソッド何かenumが指定を求めてるぞ?
何かこのリスト見た事あるな・・・
public enum Hook
{
OnCompileEnd = 0,
OnEnteredPlayMode,
OnExitingPlayMode,
OnPlayButHasError,
OnPreProcessBuild,
OnPostProcessBuildSuccess,
OnPostProcessBuildFailed,
OnSave,
OnBuildTargetChanged,
}
あ、これさっきVoiceerのウィンドウでボイス設定してたUnityのイベントと同じ内容では?
じゃあきっと指定したイベントに入ってるボイスが再生されるのでは!?
スクリプトに戻るとこんな感じかな?
Voiceer.SoundPlayer.PlaySound (Voiceer.Hook.OnCompileEnd);
取り合えずコンパイル終了時のを入れて見た。何か行けそうな気がする!
という事でさっきのStartボタンの所に入れて見よう!
private void DrawStartButton ()
{
// ボタン表示
if (GUILayout.Button ("Start", GUILayout.Height (30f)))
{
// ボタンが押されたので処理を行う
Debug.Log ("Start! namespace = " + m_namespaceName);
// ファイル一覧を作る
List<string> fileList = CreateFileList ();
int count = fileList.Count;
if (count <= 0)
{
Debug.Log ("ファイルがありませんでした");
return;
}
// デバッグ用にファイル一覧を書き出す
for (int i = 0; i < count; ++i)
{
Debug.Log ("File = " + fileList[i]);
}
// namespace入れ込む
NamespaceInserter insterter = new NamespaceInserter ();
insterter.InsertNamespace (m_namespaceName, fileList, m_isOverwrite);
// ファイル更新
AssetDatabase.Refresh ();
// Voiceer経由でボイス再生(取り合えずコンパイル終わりボイス)
Voiceer.SoundPlayer.PlaySound (Voiceer.Hook.OnCompileEnd);
// 終了&ダイアログ表示
Debug.Log ("Finished inserting!");
EditorUtility.DisplayDialog (WINDOW_NAME, ON_COMPLETE_MESSAGE, "OK");
}
}
さあ、動くか!?
喋ったー!! ♪└(∵┌)♪└( ∵ )┘♪(┐∵)┘♪
ただ、コンパイルのボイスの後にnamespaceが変わった事によってコンパイルが走り、同じボイスが呼ばれていました・・・何かあれなので、他カテゴリでも試してみましょう。取り合えず保存時?
// Voiceer経由でボイス再生
Voiceer.SoundPlayer.PlaySound (Voiceer.Hook.OnSave);
さあ、どうだ!?
行けた!
進捗どうですか!?
ダメです!!!!
_:(´ཀ`」 ∠):_
・・・取り合えず自作エディタ拡張でもボイス再生を鳴らせました!
Voiceerのカテゴリを増やしてみる
自作エディター拡張にVoiceerの組み込めた事によりQOLが更に爆上げした状態ですが、少し使ってるとUnityのデフォルトイベントだと自作エディター拡張の機能と微妙に欲しいボイスがかみ合わないのでは?と贅沢な事を考え出してしまいました・・・
となると、独自のカテゴリーを作ってそれにボイスを当てはめて行くのが良いのでは無いか?
という事でカテゴリーの増やし方を探してみようと思います!
目指せ!僕が考えたさいつよのゆにてぃ!
てな訳でコードを考えるのですが、最初に探すのは先ほどイベント一覧があったenumですよね。取り合えずあそこに新しいイベント名を追加すればコード上は追加されるのでは?という事で足して見よう。
先ほどのenumがあるのはどうやらVoicePreset.csというファイルらしいので、開いて足すぜ
public enum Hook
{
OnCompileEnd = 0,
OnEnteredPlayMode,
OnExitingPlayMode,
OnPlayButHasError,
OnPreProcessBuild,
OnPostProcessBuildSuccess,
OnPostProcessBuildFailed,
OnSave,
OnBuildTargetChanged,
Monafuwa, // すい~とる~むを追加
}
さあ、どうだ?ボイス設定ウィンドウを開いて見て見よう
お、もなふわが追加されている!何か動きそう!!
恐らくenumの文字列がそのまま表示されているので、先に日本語化して見たい気がする。という事で変換してる所を探してみよう。
他の既存のenumを使ってる所をスクリプトから検索すればきっと引っかかるよな・・・?じゃあ取り合えずHook.OnCompileEndとか探してみよう。
2件。上の方は明らかにUnityのイベントに先ほどのSoundPlayer.PlaySoundでボイス再生を足しているコードなので、もう片方を見よう
public static string GetDescriptionLabel(Hook trigger)
{
switch (trigger)
{
case Hook.OnCompileEnd:
return "コンパイルが終了した時";
case Hook.OnEnteredPlayMode:
return "正常に再生できた時";
case Hook.OnExitingPlayMode:
return "再生モードを終了した時";
case Hook.OnPlayButHasError:
return "再生しようとしたがエラーがあって再生できなかった時";
case Hook.OnPreProcessBuild:
return "ビルドプロセスに入る直前";
case Hook.OnPostProcessBuildSuccess:
return "正常にビルドが完了した時";
case Hook.OnPostProcessBuildFailed:
return "ビルドが失敗した時";
case Hook.OnSave:
return "プロジェクトをセーブした時";
case Hook.OnBuildTargetChanged:
return "ビルドターゲットを変更した時";
default:
//上記で未定義なTriggerはそのまま出力
return trigger.ToString();
}
}
お、これはenumを元に日本語の文字列を返してるぞ?つまりここにさっき追加したenumのやつを追加すれば日本語化できるのでは?やってみよう
public static string GetDescriptionLabel(Hook trigger)
{
switch (trigger)
{
case Hook.OnCompileEnd:
return "コンパイルが終了した時";
case Hook.OnEnteredPlayMode:
return "正常に再生できた時";
case Hook.OnExitingPlayMode:
return "再生モードを終了した時";
case Hook.OnPlayButHasError:
return "再生しようとしたがエラーがあって再生できなかった時";
case Hook.OnPreProcessBuild:
return "ビルドプロセスに入る直前";
case Hook.OnPostProcessBuildSuccess:
return "正常にビルドが完了した時";
case Hook.OnPostProcessBuildFailed:
return "ビルドが失敗した時";
case Hook.OnSave:
return "プロジェクトをセーブした時";
case Hook.OnBuildTargetChanged:
return "ビルドターゲットを変更した時";
case Hook.Monafuwa:
return "もなふわすい~とる~む"; // もなふわを追加してみる
default:
//上記で未定義なTriggerはそのまま出力
return trigger.ToString();
}
}
一番下のdefaultの前にもなふわを足して見ます。さあ、どうだ?
来た!これで新しいカテゴリーにボイスを足してみて
さっきのnamespace追加ボタンのボイスカテゴリー指定をもなふわに変えてみるぞい
private void DrawStartButton ()
{
// ボタン表示
if (GUILayout.Button ("Start", GUILayout.Height (30f)))
{
// ...色々省略
// Voiceer経由でボイス再生
Voiceer.SoundPlayer.PlaySound (Voiceer.Hook.Monafuwa);
// 終了&ダイアログ表示
Debug.Log ("Finished inserting!");
EditorUtility.DisplayDialog (WINDOW_NAME, ON_COMPLETE_MESSAGE, "OK");
}
}
さあ、出るか!?
たすけて!もなふわすい~とる~む!!
という事で強制タスケテカテゴリーが作れました!
これで何時でも助けが呼べちゃうんだぜ!!
こんな感じでenumにカテゴリー追加と日本語ラベルを返す処理に日本語を追加すれば好きにカテゴリー追加し放題なので、さいつよのUnityが作れそう!
QOLが更に上がって爆アドヒャッホイ!デス!!
注意事項
チーム制作をしている際、うっかりgitignoreに入れ忘れて、更にまとめてファイルをアップしてしまうと、他の人のUnityももなふわすい~とる~む化するので気を付けましょう
別の捉え方をすると、アップさえしてしまえば強制的に推しを布教できるとも考えられますね!(ダメ
4時やんけアラーム
仕事や個人製作をしている際、気が付いたら4時やんけしている時とかよくありますよね(断定)。
そんな時用に(?)巻乃さんは何と「4時やんけ」アラームボイスを提供してくれています!大天使モナエルちゃんマジ天使!!
これが7/4に公開されまして。これを見た某牛アイコンの人がこんな事を言ってました
(なんで午前3時にこんな事してるんでしょうね?時期的に多分この直前に仕事上がりだったろうから寝ろよ)
となって制作した結果、こうなりました
と言う事で、実装は超雑ですが4時やんけアラームの使用例として、どうやって作ったかを超ザックリと紹介したいと思います。
まず、何時にアラームを鳴らしたいかとかを設定出来た方が良いので、それ用のデータを作ります。適当にScriptableObjectを定義します
using System.Collections.Generic;
using UnityEngine;
namespace MaxNeet.YojiYanke
{
public class TimerData
: ScriptableObject
{
[System.Serializable]
public class Param
{
public int Hour = 4; // 時
public int Min = 0; // 分
//public int Seconds = 0; // 秒は面倒になったので固定で0
public string Message = string.Empty; // 表示する文字
public AudioClip Voice = null; // 再生するボイス
}
[SerializeField]
List<Param> m_data = null;
public List<Param> Data => m_data;
}
}
そしてこのTimerData用のScriptableObjectを適当に作成し(省略)、設定を当て込んで行きます。4時やんけなので4amに設定しましょう
データを作ったので、これを読み込んで保持する機能を作ります。超雑実装なので、名前決め打ちで作ってましたね過去の自分・・・
namespace MaxNeet.YojiYanke
{
public class TimerDataStorage
: ScriptableSingleton<TimerDataStorage>
{
TimerData m_data = null;
public TimerData Data => m_data ?? LoadData (); // データ
public DateTime LastPlayedTime { get; set; } = DateTime.Today; // 最後にアラームが鳴った時間は?
public bool HasPlayedOnce { get; set; } = false; // 起動後1回はアラームが鳴ったか?
public TimerData LoadData ()
{
m_data = AssetDatabase.LoadAssetAtPath<TimerData> ("Assets/MaxNeet/Yoji-Yanke/Editor/Data/TimerData.asset");
if (m_data == null)
{
var guid = AssetDatabase.FindAssets ($"t:TimerData").FirstOrDefault ();
var path = AssetDatabase.GUIDToAssetPath (guid);
m_data = AssetDatabase.LoadAssetAtPath<TimerData> (path);
}
return m_data;
}
}
}
ザックリと解説しますと、まずこのクラスはScriptableSingletonというUnityが生きている間は生きているエディター専用のオブジェクトです(雑)。
この辺は前にここで書いた気がしなくもないので別例が気になったらみてみてください
それでロード処理が呼ばれたら上記のScriptableObjectを読み込んでいます。パスや名前直打ちヒドイですね~。
ではデータが用意出来たので(ロードは後で)タイマー部分を見て見たいと思います。タイマー部分はザックリと定期的に呼ばれる処理の中で現在の時間を見て、それがデータ内にある時間と一致していたら何かをする。みたいな流れを組んでいます
namespace MaxNeet.YojiYanke
{
public class TimerProcessor
{
public static void Process ()
{
var now = DateTime.Now; // 今の時間
var previous = TimerDataStorage.instance.LastPlayedTime; //前回の時間
var dataList = TimerDataStorage.instance.Data; // アラーム再生データ一覧
foreach (var data in dataList.Data)
{
// 時間が違うか
if (data.Hour != now.Hour || data.Min != now.Minute)
{
// 現在時刻と違うので、スルー
continue;
}
// 前回再生と同じ時間じゃないかチェック
if (TimerDataStorage.instance.HasPlayedOnce
&& now.Hour == previous.Hour
&& now.Minute == previous.Minute
)
{
// さっき鳴らしたばかりやんけ
continue;
}
// 条件満たしたのでサウンド再生
bool isSucces = EditorSoundPlayer.PlaySound (data.Voice);
if (isSucces)
{
// 再生したら同じ時刻の再生回避の為に時間を記録する
TimerDataStorage.instance.HasPlayedOnce = true;
TimerDataStorage.instance.LastPlayedTime = now;
// ウィンドウを開く
TimerWindow.OpenFromData (data);
}
}
}
}
}
一応4時0分の間何度も再生しないように前回鳴らした時間を保持して同じ時間の場合は鳴らさないようにしています。
ではこの確認処理を定期的に呼ぶ登録をしている所を見て見ましょう
namespace MaxNeet.YojiYanke
{
public static class TimerCaller
{
[InitializeOnLoadMethod]
private static void CallTimer ()
{
// データのロード
TimerDataStorage.instance.LoadData ();
// アラームチェック処理を登録
EditorApplication.update += () =>
{
TimerProcessor.Process ();
};
}
}
}
ここに出てくる [InitializeOnLoadMethod]がついているメソッドはUnity起動時に呼ばれるメソッドです。なので今回の登録処理はUnity起動時に一度だけ行っています。
この中で先ほどスルーしたデータのロード処理を行っています。ここで呼ぶことでこれ以降アラームの時間確認の時にデータを使えるようになります。
EditorApplication.updateはUnityのエディターが更新する時のイベントです。ここで先ほど作成したアラームの再生確認を入れる事で定期的に今が再生する時かを確認する事が出来ています。
基本機能は大体できて、最後にサウンド再生とウィンドウの表示なのですが、ウィンドウの表示はオマケなのでスルーします。是非楽しめそうな表現を作ってみてください。
サウンドなのですが、一応当時はそれ用の機能を作りましたがこれを書いている今(2020/12/09 0:55)この瞬間に、書きながら「上記したVoiceerにカテゴリー追加して再生させた方が圧倒的に楽じゃね?」という事実に気が付いてしまった為、それをオススメさせて頂きます・・・ぎゃふん
という事で4時やんけアラームの使用例でアラームを作ってた流れを紹介というか、雑にコード紹介したやつでした!(雑ですまないが、コードも雑過ぎた・・・
これで皆作業に没頭しても4時だという事に気が付けるね!やったね!!
ちなみ某牛さんは何故か次の日の4時にまたこんな更新を入れて遊んでました・・・寝ろよ
最後に
思ってたより長くなりましたが、こんな感じで巻乃さんが提供してくださっている素晴らしいボイスをUnityに組み込んで行き、Unityでの制作QOLを爆上げする方法を紹介&模索してみました!
ところで作業をしながらもなふわすい~とる~むを開いている事は皆さんあると思うのですが
このツイ時は使って無いですが、巻乃さんボイスが入ってるUnityで作業していると巻乃さんが2重に喋るスペシャル素敵空間になるので、そいう事も起こる様になります。
なので(?)、皆さんもドンドン巻乃さんのボイスをUnityに入れ込んで開発でMP削りながら巻乃さんの癒しボイスでMP回復しましょう!!
巻乃もなかはイイぞ
余談ですがVoiceerのカテゴリー追加は書いてる最中に「出来たら便利だよな・・・」と思い、記事を書きながら実装したぐらい簡単に扱える良いツールなので是非皆さんも使ってみてね!
・・・結果記事としては色々とまとまりが無くなってしましましたが
裏の本題
Unity開発のQOLを更に上げる為に巻乃さんに新しいフォルダー(3)、もといVoiceer等用のボイス素材を依頼(有償)したい欲があるのですが、誰か一緒に内容考えたり、依頼出したりしませぬか・・・?