Last active
May 19, 2018 05:40
-
-
Save Tsumio/e198e7dfe1c9cbdfc32e4d929a356007 to your computer and use it in GitHub Desktop.
EmofuriStaticLipSynch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
/// <summary> | |
/// リップシンクのソースを表すインターフェイス | |
/// </summary> | |
public interface ILipSynchSources { | |
////============================================================================= | |
//// Properties | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンクするCSVデータのファイル名を返す | |
/// </summary> | |
string FileName { get; } | |
////============================================================================= | |
//// Public Method | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンク開始時に呼ぶべきメソッド | |
/// </summary> | |
/// <param name="audioSource">親のAudioSource</param> | |
/// <param name="endValue">再生終了時のface_talkの値</param> | |
void StartLipSynch(AudioSource audioSource, float endValue); | |
/// <summary> | |
/// Updateメソッドで呼ぶべきメソッド | |
/// </summary> | |
void UpdateLipSynch(); | |
/// <summary> | |
/// リップシンクを中断する | |
/// </summary> | |
void AbortLipSynch(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using System; | |
/// <summary> | |
/// リップシンクのソースをまとめているクラス。 | |
/// </summary> | |
[Serializable] | |
public class LipSynchSources : ILipSynchSources { | |
////============================================================================= | |
//// Local Field | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンクさせたいえもふりのオブジェクト本体 | |
/// </summary> | |
[SerializeField] | |
private EmotePlayer _emotePlayer; | |
/// <summary> | |
/// 再生したい音声 | |
/// </summary> | |
[SerializeField] | |
private AudioClip _audioClip; | |
/// <summary> | |
/// リップシンクのデータが入っているcsvファイル | |
/// </summary> | |
[SerializeField] | |
private TextAsset _csvSource; | |
/// <summary> | |
/// リップシンクのデータを出力したときのFPS | |
/// </summary> | |
[SerializeField] | |
private float _fps = 60.0f; | |
/// <summary> | |
/// 1フレームにかかる秒数(リップシンクのデータの1区切りはこの秒数で経過する) | |
/// </summary> | |
private float _eachFrameSec = 0; | |
/// <summary> | |
/// 経過した秒数 | |
/// ここにTime.deltaTimeを加算して、EachFrameSecより小さくなるまでDequeueさせる | |
/// </summary> | |
private float _elapsedSec = 0; | |
/// <summary> | |
/// リップシンク用のCSVデータを保持するオリジナルのキュー | |
/// </summary> | |
private Queue<float> _originCsvData; | |
/// <summary> | |
/// リップシンク中に利用するCSVデータを保持するキュー | |
/// ここにオリジナルのキューをコピーする | |
/// </summary> | |
private Queue<float> _currentCsvData; | |
/// <summary> | |
/// リップシンクを開始するかどうか | |
/// </summary> | |
private bool _shouldPlayLipSynch; | |
/// <summary> | |
/// リップシンク終了時のface_talkの値 | |
/// </summary> | |
private float _endValue = 0; | |
/// <summary> | |
/// 対象の変数ラベル | |
/// </summary> | |
private readonly string _variableLabel = "face_talk"; | |
////============================================================================= | |
//// Properties | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンクするCSVデータのファイル名を返す | |
/// </summary> | |
public string FileName { | |
get { | |
return _csvSource.name; | |
} | |
} | |
/// <summary> | |
/// リップシンク用のCSVデータを保持するキュー | |
/// </summary> | |
private Queue<float> OriginCsvData { | |
get { | |
if(_originCsvData == null) { | |
InitializeQueue(); | |
} | |
return _originCsvData; | |
} | |
set { | |
_originCsvData = value; | |
} | |
} | |
/// <summary> | |
/// 1フレームにかかる秒数(リップシンクのデータの1区切りはこの秒数で経過する) | |
/// </summary> | |
private float EachFrameSec { | |
get { | |
if(_eachFrameSec == 0) { | |
_eachFrameSec = 1.0f / _fps; | |
} | |
return _eachFrameSec; | |
} | |
} | |
/// <summary> | |
/// リップシンクのデータを保存しているキューをDequeueすべきかどうか | |
/// </summary> | |
private bool ShouldDequeue { | |
get { | |
return _elapsedSec > EachFrameSec; | |
} | |
} | |
/// <summary> | |
/// 現在のCsvデータがキューを保持しているかどうか | |
/// </summary> | |
private bool HasQueue => _currentCsvData.Count > 0; | |
////============================================================================= | |
//// Public Method | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンク開始時に呼ぶべきメソッド | |
/// </summary> | |
public void StartLipSynch(AudioSource audioSource, float endValue = 0) { | |
//オリジナルキューをコピーする(参照は別にする) | |
_currentCsvData = new Queue<float>(OriginCsvData); | |
//親のAudioSourceのクリップに、適切なAudioClipを設定する | |
audioSource.clip = _audioClip; | |
//リップシンクを開始するフラグを立て、音声も再生する | |
_shouldPlayLipSynch = true; | |
_endValue = endValue; | |
_elapsedSec = 0; | |
audioSource.Play(); | |
} | |
/// <summary> | |
/// リップシンクを更新する | |
/// </summary> | |
public void UpdateLipSynch() { | |
//リップシンクするべきでなければ即リターン | |
if(!_shouldPlayLipSynch) { | |
return; | |
} | |
//CSVデータがなくなるまでDequeueしてリップの動きを更新する | |
if(HasQueue) { | |
RefreshLip(); | |
} else { | |
EndLipSynch(); | |
} | |
} | |
/// <summary> | |
/// リップシンクを中断する | |
/// </summary> | |
public void AbortLipSynch() { | |
_shouldPlayLipSynch = false; | |
} | |
////============================================================================= | |
//// Private Method | |
//// | |
////============================================================================= | |
/// <summary> | |
/// セットされたCSVデータをもとにキューを作成する | |
/// 例外処理は特にしていない(無効なデータなら早めに落としたいため) | |
/// </summary> | |
/// <returns></returns> | |
private void InitializeQueue() { | |
//余計な空白を削ってから「,」で分割 | |
var csvArray = _csvSource.text.Trim().Split(','); | |
//キューにCSVデータを登録していく | |
var tempQueue = new Queue<float>(); | |
foreach(var csv in csvArray) { | |
tempQueue.Enqueue(float.Parse(csv)); | |
} | |
//完成したキューを代入する | |
OriginCsvData = tempQueue; | |
} | |
/// <summary> | |
/// リップの動きを新しいものに変える | |
/// </summary> | |
private void RefreshLip() { | |
//経過時間を加算 | |
_elapsedSec += Time.deltaTime; | |
//経過時間分だけDequeueする | |
while(ShouldDequeue) { | |
_emotePlayer.SetVariable(_variableLabel, _currentCsvData.Dequeue()); | |
_elapsedSec -= EachFrameSec; | |
} | |
} | |
/// <summary> | |
/// リップシンクを終了する | |
/// </summary> | |
private void EndLipSynch() { | |
//CSVデータがなくなったら、リップシンクするべきでないというフラグを立てる | |
//Note:CSVデータがなくなったときに口を完全に閉じるようにしているが、開きたい場合もあるか? | |
_emotePlayer.SetVariable(_variableLabel, _endValue); | |
_shouldPlayLipSynch = false; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using System; | |
using System.Linq; | |
/// <summary> | |
/// 静的なリップシンクをおこなうためのクラス。 | |
/// 1キャラクターが1つのAudioSourceを持つことを前提として作られている。 | |
/// つまり、1キャラクターは同時に1つまでしかリップシンクしない(他キャラクターとは排他的な関係にある)。 | |
/// </summary> | |
public class TsumioLipSynchController : MonoBehaviour { | |
////============================================================================= | |
//// Local Field | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンク用のCSVデータを保持するキュー | |
/// </summary> | |
private Queue<float> _csvData; | |
/// <summary> | |
/// リップシンクのソースたち | |
/// </summary> | |
/// Note:ILipSynchSourcesを実装しているクラスなら何でもOKなのだが、具象クラスでないとUnityのインスペクタに表示されないのでこうしている。 | |
/// なので、ILipSynchSourcesを実装している他クラスに付け替えることは問題ない(例えば外部のストレージからデータをもってくるクラスが考えられる) | |
[SerializeField] | |
private List<LipSynchSources> _sources; | |
/// <summary> | |
/// 音声を再生するのに使うAudioSource。 | |
/// </summary> | |
[SerializeField] | |
private AudioSource _audioSource; | |
/// <summary> | |
/// ソースとファイル名を結びつけているディクショナリ | |
/// </summary> | |
private IDictionary<string, LipSynchSources> _sourceDict; | |
/// <summary> | |
/// 前回のリップシンク名。中断処理に使用する | |
/// </summary> | |
private string _prevLipSynchName; | |
////============================================================================= | |
//// Properties | |
//// | |
////============================================================================= | |
/// <summary> | |
/// ソースとファイル名を結びつけているディクショナリ | |
/// </summary> | |
private IDictionary<string, LipSynchSources> SourceDict { | |
get { | |
if(_sourceDict == null) { | |
InitializeSourceDictionary(); | |
} | |
return _sourceDict; | |
} | |
} | |
////============================================================================= | |
//// MonoBehaviour | |
//// | |
////============================================================================= | |
public void Update() { | |
//リップシンクの更新 | |
_sources.ForEach(s => s.UpdateLipSynch()); | |
} | |
////============================================================================= | |
//// Public Method | |
//// | |
////============================================================================= | |
/// <summary> | |
/// fileNameのCSVファイルをリップシンクさせ、音声を再生する。 | |
/// リップシンク終了時のface_talkの値は0。 | |
/// </summary> | |
/// <param name="fileName">リップシンクさせたいCSVファイル名</param> | |
public void StartVoiceAction(string fileName) { | |
StartVoiceAction(fileName, 0); | |
} | |
/// <summary> | |
/// fileNameのCSVファイルをリップシンクさせ、音声を再生する | |
/// </summary> | |
/// <param name="fileName">リップシンクさせたいCSVファイル名</param> | |
/// <param name="endValue">リップシンク終了時のface_talkの値</param> | |
public void StartVoiceAction(string fileName, float endValue) { | |
//ソースファイルが存在しないなら、即リターン | |
if(!TryGetSource(fileName)) { | |
return; | |
} | |
//前回のリップシンクを中断する | |
AbortPrevLipSynch(_prevLipSynchName); | |
//今回のリップシンク名を保存 | |
_prevLipSynchName = fileName; | |
//リップシンクのアクションを開始 | |
StartLipSynch(SourceDict[fileName], endValue); | |
} | |
/// <summary> | |
/// ランダムにCSVファイルを選択し、音声を再生する | |
/// </summary> | |
public void StartVoiceActionAtRandom() { | |
var fileName = SourceDict.ElementAt(UnityEngine.Random.Range(0, SourceDict.Count)).Value.FileName; | |
StartVoiceAction(fileName); | |
} | |
////============================================================================= | |
//// Private Method | |
//// | |
////============================================================================= | |
/// <summary> | |
/// リップシンク音声をセットし、音声の再生を開始する | |
/// </summary> | |
/// <param name="target"></param> | |
private void StartLipSynch(ILipSynchSources target, float endValue) { | |
target.StartLipSynch(_audioSource, endValue); | |
} | |
/// <summary> | |
/// 前回のリップシンクを中断する | |
/// </summary> | |
private void AbortPrevLipSynch(string fileName) { | |
//無効な内容ならば即リターン | |
if(string.IsNullOrEmpty(fileName)) { | |
return; | |
} | |
//中断処理をする | |
SourceDict[_prevLipSynchName].AbortLipSynch(); | |
} | |
/// <summary> | |
/// ソース用のディクショナリを初期化する | |
/// </summary> | |
private void InitializeSourceDictionary() { | |
_sourceDict = new Dictionary<string, LipSynchSources>(); | |
_sources.ForEach( s => SourceDict.Add(s.FileName, s)); | |
} | |
/// <summary> | |
/// ソースファイルが存在するかどうかを返す | |
/// </summary> | |
private bool TryGetSource(string fileName) { | |
//ファイルが存在するならtrue | |
if(SourceDict.ContainsKey(fileName)) { | |
return true; | |
} | |
//存在しないなら警告を表示し、falseを返す | |
Debug.LogWarning($"{fileName}という名前のCSVファイルは設定されていません。設定されている全てのファイル名は次の通りです。"); | |
foreach(var s in SourceDict) { | |
Debug.LogWarning(s.Value.FileName); | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment