はじめに
Unityで 縦書きの文字を 音楽に合わせて表示する
という夢をみたので
軽く実装してみた。
音楽xノベル とか新しいジャンルにならないかなと言う感じ。
作成環境:Mac Unity 2018.1.7f1
動作結果
音の再生開始時点で成功・失敗判定してしまうと1回聴き流さないといけないので、イマイチに。。 インタラクティブミュージックならではの悩みどころかも・・・。 判定のタイミングをなるべくぎりぎりまで遅らせるようにするとか、だいぶ面倒な予約再生になりそうだ。 pic.twitter.com/kKrjWlpsRi
— tatmos (@tatmos) 2019年5月11日
こんな感じ。
音楽と文字の出るタイミングが同期していて気持ち良いのではないだろうか?
使った技術
Unityのスケジュール再生PlayScheduledを使って、イベントに先行して音の再生リクエストし、待機状態にしておくことで、
音楽のタイミングがほぼずれない演出になる。
余談:代わりにAudioSourceをオーバーラップしている分余計にリソースつかったり、それらをうまく外側で管理したりとかちょっと手を加えたりする必要もある。
けど、Time.timeSinceLevelLoadとかでなんとなく作るとだいたい音楽側が綺麗に繋がらなくて破綻する。あと、AudioSettings.dspTimeとかもあんまりいろんなところで呼び出すと処理負荷があがりそうな気はする。(タイミング取得でおそらく様々な処理がブロックされたりしているはず・・・オーディオの処理はゲームの処理と別なところで動いているはずなので、ここら辺の機能使う時は要注意。今回はあまり気にせずとにかく動かしているという感じ)
データ構造
音素材は、それぞれの段落ごとに用意し、段落の長さの波形を用意するだけというシンプルな構造。
段落を1イベントとして、サウンドとテキスト(テキストはスクリプトに直書き)
Editorで作れるのがUnityの良いところだけど、数が増えると難儀で、どこかでスクリプトなり、csvやexelなどテキストに頼ることになるとは思う。
今回は、動作をするのか試したかったので、動くこと優先でとにかく簡易に実装できるようにしている。
コアのスクリプト
楽に実装したかったので、GameMainというオブジェクトを作って、そこにスクリプトを貼り付けて制御。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
public Text text;
double nextEventTime = 0;
private int flip = 0;
List<string> eventData = new List<string>();
public List<AudioClip> clipData = new List<AudioClip>();
private AudioSource[] audioSources = new AudioSource[2];
// 文字の出現回数をカウント
public static int CountChar(string s, char c)
{
return s.Length - s.Replace(c.ToString(), "").Length;
}
// Use this for initialization
void Start()
{
for (int i = 0; i < 2; i++)
{
GameObject child = new GameObject("Player");
child.transform.parent = gameObject.transform;
audioSources[i] = child.AddComponent<AudioSource>();
}
nextEventTime = AudioSettings.dspTime + 0.0f;
eventData.Add("はじまりは、勝手に、進む1");
eventData.Add("次の段落も、勝手に、進む2");
eventData.Add("次の段落も、勝手に、進む3");
eventData.Add("次の段落も、勝手に、進む4");
eventData.Add("次の段落も、勝手に、進む5");
eventData.Add("次の段落も、勝手に、進む6");
eventData.Add("次の段落も、勝手に、進む7");
eventData.Add("次の段落も、勝手に、進む8");
eventData.Add("ラストは繰り返し");
eventData.Add("倒した!");
}
int eventNo = -1;
int lastCount = 0;
// Update is called once per frame
void Update()
{
if (nextEventTime < AudioSettings.dspTime)
{
if (eventData.Count > eventNo)
{
bool nextFlag = false;
if (eventNo == 8)
{
// 8回目だけ何回か繰り返す
lastCount++;
if (lastCount > 3)
{
nextFlag = true;
}
}
else
{
nextFlag = true;
}
if (eventNo >= 0) // 初回は何もしない
{
// テキスト表示
text.text += "\n";
text.text += eventData[eventNo];
} else {
eventNo++;
return;
}
if (eventNo == 9)
{
eventNo++; return; // 配列外にして終わり
}
// 音は次のイベントの音を先行して再生させる。(遅延して再生)
audioSources[flip].clip = clipData[eventNo + (nextFlag ? 1 : 0)]; // 次のクリップを指定
nextEventTime += clipData[eventNo].length; // 今のクリップの時間を指定
audioSources[flip].PlayScheduled(nextEventTime); // 予約再生
flip = 1 - flip;
if(nextFlag)eventNo++;
}
}
}
}
縦書きについて
Unityが縦書き対応してくれたらいいのだけど、無いっぽいので、検索してみつけた方法で実装。
UI.Text (uGUI) で縦書き(のようなもの)を作る
これも真面目にやろうとすると設定が大変だろうなぁ・・・とは思う。
位置の調整などは、フォントにも依存すると思う。
というか、位置調整も含めた縦書きフォントとかを使えたら良いのかも。ちょっと方法がわからないけど。
利用したフォントはs1-mplus-2c-mediumを使用しています。
再配布も可なところが、こういうプログラムの共有する時にとても助かる。
(余談:でも、本当は、ちゃんと作者に何かしら還元されるといいのだろうけどなぁ。リソース含めたノウハウの共有が簡単に
できると良いのだが・・・)
(余談2:Unityもこれくらいシンプルだと丸ごと共有できるけど、有料のAssetやライセンスが複雑な素材が絡むととたんに共有しにくくなって惜しいなぁと思ったり。)
あと、とっても細かいことだけど、Unityたまに文字がにじむので、fontサイズを少し大きめにしてUIを縮小するみたいな設定にしている。
(余談:ここらへんももっと何も考えずにいい感じになっていると楽なんだけどなぁ・・・)
(余談:この縦書き処理もけっこう重い。ここで紹介しているのだとテキスト量が無駄にどんどん増えていくので、これだけでも処理が重いはず。)
音素材について
音素材、テキスト、全部自作していますが、
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
(GitHubでソースはMIT Licenceにしてますけど、素材は自由にどうぞ。ゲームジャムとか同じようなインタクティブミュージクのテストの素材や共有素材にくらいなら使えるかも)
GitHub
プロジェクトごと上げてみたので、参考にしてみてください。
https://github.com/tatmos/TextNovelRythmGame20190511
おわりに
タイミングを合わせるのは難しいなぁと。 ゲーム的に処理するとしたら、予約再生のための受付猶予時間などの細かい設定が必要と思われる。
今回は、とりあえず、音素材の用意を楽にして、
音素材のタイミングで動くプログラムの最初の雛形みたいなものにはなっていると思う。(かなり適当ですが)
音同期させる場合の何かのヒントになれば良いかなぁ。。。
このままだと使いどころがあまり無いけど
ちょっと改良してやれば
テキストや音を差し替えるだけで、ちょっと変わった表現のコンテンツが作れる基本になったかと思う。
いろいろ遊んでもらえたら幸い。