UnityでVRコスプレするときに詰まったところの覚え書

100%自分用に書いたので記事内容のブラッシュアップをしていません。

# [MMD4Mecanim](http://stereoarts.jp/)
MMD用モデルをUnityで扱えるように変換してくるアセット（？）

## 問題
1. 変換されたモデルデータはMMDモデルと同じ場所に置かないとテクスチャが読み込まれない  
（同じ場所というかMMD4Mecanimで指定されたデフォルトフォルダ）

# [UnityCam](https://github.com/mrayy/UnityCam)
Unity内のCameraにアタッチするとその映像をWebカメラのように扱える

## 問題
1. なぜかサンプルが動作しない  
→昔レジストリを弄りすぎて正常に登録できなかった？OS再インストールで解決

## 使い方（普通のプロジェクト）
1. `RunMe First` をやる
1. `UnityCam-master\UnitySample` をプロジェクトのAssetsフォルダにぶち込む 
1. `UnitySample\Assets\UnityCam\Scripts` にある `UnityCam.cs` を任意のCameraにアタッチ
1. プロジェクトをRunしてOBSなどで映像を取得する

## 使い方（VRプロジェクト）
1. Canvasを配置して子に `UI -> Raw Image` を置く
1. これを無効化してGameウィンドウに表示されないようにする
1. Assetsフォルダ以下にRender Textureを作成してSizeを指定する
1. Raw ImageにRender Textureをアタッチ

## 使い方（ゲーム内で見る方法）
1. Planeを作成する
1. マテリアルのところにRaw Textureをアタッチ

[ココ](http://mikasa.hatenablog.jp/entry/2015/05/05/051008)を参考にしました

## 注意
1. `SteamVR -> CameraRig -> Camera(head)` の子としてUnityCamをアタッチしたCameraを配置するとプレイヤー視点の映像が得られる。ただしフレームレートが落ちてVR酔いする。  
→素直にGameウィンドウをキャプチャすることでこの問題を回避

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">V　R　カ　メ　コ <a href="https://t.co/dZaR3Y6SiJ">pic.twitter.com/dZaR3Y6SiJ</a></p>&mdash; karukaru＠次はAMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/956769376211881984?ref_src=twsrc%5Etfw">2018年1月26日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# WebCamTexture
Webカメラなどの外部から入力された映像をテクスチャとして貼り付けられる
HDMIキャプチャからゲームの映像を取得したい

~~[ココ](https://book.mynavi.jp/manatee/detail/id=59772)とか参考にすればいいんじゃないかな~~
[コッチ](http://unitygeek.hatenablog.com/entry/2017/04/18/093734)のほうが詳しい

## 問題
1. Unity内で映像を確認できたけど、ライティングの影響で影があると見えない  
→シェーダーの設定を変えることで解決  
Unlitはアンライティング、つまり光源に影響されないシェーダーということ。  
![スクリーンショット (75).png](https://qiita-image-store.s3.amazonaws.com/0/213883/d1518945-fcd4-dad5-3c38-f91dac8ff922.png)

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">バーチャル空間でスプラトゥーン <a href="https://t.co/VGAQwgYcQz">pic.twitter.com/VGAQwgYcQz</a></p>&mdash; karukaru＠次はAMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/957578634218299392?ref_src=twsrc%5Etfw">2018年1月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# Microphone
音声入力デバイスをUnity内で鳴らす
[ココ](https://qiita.com/niusounds/items/b8858a2b043676185a54)の最初を参考にしてゲーム音声をUnity内で流したい

## 問題
1. なぜかデバイスの指定ができない（ちなUnity2017.3.0f3）   
Microphone.devicesの[0]番目しか指定できない。デバイス名による指定（Microphone.devices[1]とか）をしても反映されない。~~nullでもデフォルトデバイスに指定されない。  
→デバイス名を変更することで暫定的な解決~~  
![スクリーンショット (61).png](https://qiita-image-store.s3.amazonaws.com/0/213883/2bbede8d-8c9a-71f0-573d-121857acd597.png)  
配列に入るデバイスは名前順というわけでもない。~~デバイス名の先頭に「あ」と入れておけばとりあえず先頭に来る。~~  
配列の先頭はデフォルトデバイス。それ以降はマジで謎な並び。
素直に諦めてキャプチャ音を直で聞いたほうがよさそう  
2018/2/2追記：UnityのIssue Trackerを見てみたら[バグ報告](https://issuetracker.unity3d.com/issues/microphone-dot-start-is-not-recording-the-audio-from-selected-recording-device)されてました。のでUnity側の修正待ちです。暫定的にバージョンを落としてもいいかも。  
→バージョンを2017.1.3f1に下げることで暫定的な対応

## できたもの
ないです

# AssetStore
古いUnityバージョンで作られたアセットは動作しない可能性が高い
特にUnity5.0以前は注意？

# [Final IK（VRIK）](https://assetstore.unity.com/packages/tools/animation/final-ik-14290)
~~買う~~　買いました（2018/1/31）
UnityでIKを扱うのにメッチャ便利なアセット
基本的には[ココ](https://qiita.com/halne369/items/daadf0e00330a1019830)と同じことをすれば問題ない

## Solverにある各値の説明
[長くなりすぎたので別記事に移動しました。](https://qiita.com/_karukaru_/items/b74bb5bdf08f5de32d0e)

## 問題
1. MMDモデルに対してVRIKをアタッチしてもReferencesを自動で設定してくれない
→モデルのAnimation TypeをHumanoidにすることで解決
![スクリーンショット (62).png](https://qiita-image-store.s3.amazonaws.com/0/213883/250b5aa6-22ec-6ce2-72d6-3a0ea4634cc0.png)
![スクリーンショット (63).png](https://qiita-image-store.s3.amazonaws.com/0/213883/058b6212-361d-877d-6c3a-91c62624b4fb.png)
参考：[そのいち](http://miyas-maincontents.blogspot.com/2016/11/final-ik.html)

1. ワープしたときにワープ前の座標に足座標を置いてくる
![vlcsnap-2018-01-31-17h58m27s527.jpg](https://qiita-image-store.s3.amazonaws.com/0/213883/2772bd76-df96-a768-2169-8a8b65926644.jpeg)
こんな感じ
~~→ワープしたときのイベントで足座標を移動してあげればよさそう（まだやってない）
→[ココ](https://qiita.com/yusuke_tashiro/items/3ff690841df6c18bb430)に解決策載ってた~~
→VRIKの設定がおかしいのが原因だった
各足の Position Weight を0にしないと原点に足が持ってかれる
（ただし追加のトラッカーなどで足の座標を取得できるなら1にする）
![スクリーンショット (70).png](https://qiita-image-store.s3.amazonaws.com/0/213883/46880044-11a1-69fd-6b68-038311145b67.png)

1. モデルと自分の体格が合わない
→モデルのScaleを少しだけ大きくすることで対応。Scaleを変えたら必ずVRIKの設定を見直すこと。[参考](https://qiita.com/chiepomme/items/aef42df2d46aa0f79fbe)
VRIKの設定を弄ることで大きく改善することがあるため、面倒でもこの設定は拘ろう。
また各種ターゲットの位置を微調整することでも改善する可能性がある。
![スクリーンショット (66).png](https://qiita-image-store.s3.amazonaws.com/0/213883/5dc63cec-65ad-24e3-bba2-2da1e549ec52.png)
![スクリーンショット (67).png](https://qiita-image-store.s3.amazonaws.com/0/213883/bef6786f-cbca-0610-6e14-434949d9759b.png)
![スクリーンショット (68).png](https://qiita-image-store.s3.amazonaws.com/0/213883/ff80bc47-add6-dad8-1ff7-adeccd278fc8.png)

# リップシンクしたい
VOICEROIDの音声に反応してリアルタイムで口が動いたら喋ってる感あっていいよね
OVRLipSyncを使いたい、のだけど名前が変わった？ 今（2018/2/1）は [Oculus LipSync Unity](https://developer.oculus.com/downloads/package/oculus-lipsync-unity/) というらしい。
でもプロジェクトにインポートしたらOVRLipSyncと表示された。お前の名前どっちだよ。

[ココ](http://tips.hecomi.com/entry/2016/02/16/202634)と[コレ](https://qiita.com/toRisouP/items/14fe62f89808013f9f6e#%E3%83%AA%E3%83%83%E3%83%97%E3%82%B7%E3%83%B3%E3%82%AF)を参考に進めれば問題ない。
ちなみに公式の結月ゆかりMMDモデルのモーフはこんな感じ
![スクリーンショット (71).png](https://qiita-image-store.s3.amazonaws.com/0/213883/0078dc73-7361-2fd8-dfc0-dea261dad89a.png)

Runするごとに音声入力デバイスを指定するのは面倒なので、デバイス名を固定にしてプログラム側で対象を探すようにちょっと改造しました。

```OVRLipSyncMicInput.cs
void Start() 
{
	audioSource.loop = true; 	// Set the AudioClip to loop
	audioSource.mute = false; 	

	if(Microphone.devices.Length!= 0)
	{
        //selectedDevice = Microphone.devices[0].ToString();
        foreach (string device in Microphone.devices)
        {
            //"Yamaha" から始まるデバイスを指定する
            if (device.StartsWith("Yamaha"))
            {
                selectedDevice = device;
            }
        }

        micSelected = true;
        GetMicCaps();
	}
}
```

## 問題
1. Microphoneのデバイスが指定できないからVOICEROIDのリップシンクができない  
→調べたらUnityのバグでした。修正待ち。詳しくは[Issue Tracker](https://issuetracker.unity3d.com/issues/microphone-dot-start-is-not-recording-the-audio-from-selected-recording-device)へ。  
→バージョンを2017.1.3f1に下げることで暫定的な対応

# [AssetStoreで買ったカメラのモデル](https://assetstore.unity.com/packages/3d/classic-film-camera-58100)
UnityCamの位置を示す用に使う

## 問題
1. コントローラでつかめない  
→Colliderが入ってなかった。Meshはあるので各パーツにMesh Colliderをアタッチして対応するメッシュを指定すればよい。  
→モノによってはStaticがオンになっている。オフにしたら動かせる。

# コントローラをUnityCamに映したくない
[ココ](http://karaagedigital.hatenablog.jp/entry/2016/09/15/183000)を参考にコントローラのモデルのレイヤーを「Controller」と設定し、UnityCamのCullingMaskでControllerを除外すればOK。

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">CameraとコントローラモデルのLayerを弄ったら、特定のカメラだけコントローラを非表示にすることができた <a href="https://t.co/cTNdk7G54a">pic.twitter.com/cTNdk7G54a</a></p>&mdash; karukaru＠次はAMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/958698771797258241?ref_src=twsrc%5Etfw">2018年1月31日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# CM3D2のメニューボタンのように、押したらコントローラの役割が変わるようにしたい
具体的には、テレポートモード、手変化モード、表情変化モードの3つを実装したい
また役割が変わったらわかりやすいようにコントローラのちょっと上あたりに通知を出したい

役割を切り替えるのは、対象がコンポーネントなら[コッチ](http://yasavegitable.hatenablog.com/entry/2013/03/01/210718)、オブジェクトなら[コッチ](https://marunouchi-tech.i-studio.co.jp/2290/)
通知を出す方法は[ココ](http://takoyakiroom.hatenablog.com/entry/2017/02/04/222834)が参考になる

## 問題
1. コントローライベントなどを追加したオブジェクトを取得できない
VRTKというGameObject直下にLeftCとRightCというコントローライベントなどをアタッチしたオブジェクトがあるのだが、これをスクリプトで外部からFindとかGetComponentできなかった。
![123.png](https://qiita-image-store.s3.amazonaws.com/0/213883/ddc0e89e-7f97-8a0c-2e23-13e93be43b7c.png)
なぜかというと、Runした状態だとVRTKによりLeftCとRightCが[Camera Rig]の各Controller配下に移動していたから。
![124.png](https://qiita-image-store.s3.amazonaws.com/0/213883/bd835015-5683-50f7-1d1d-f8c18246ec01.png)
見る場所を間違えていたため取得できていなかった。

2. 左手の指が逆パカされてしまう
右手の指の回転軸で左手の指も回転していたため、回転方向がおかしくなってしまった。マイナスをかけることで修正。

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">メニュー実装してテレポートモードと指を動かすモード作った <a href="https://t.co/trc5bEdQkN">pic.twitter.com/trc5bEdQkN</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/961585696594051078?ref_src=twsrc%5Etfw">2018年2月8日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 重い処理をしているわけではないのにフレームレートがガタ落ちしている
腕やオブジェクトを動かすと、滑らかに移動しないで若干ワープしながら移動する。
視点を揺らすと瞬間移動しているのがよくわかる。

~~[ココ](http://yaseino.hatenablog.com/entry/2016/02/23/234652)を参考にFixedUpdateのFixed Timestepを0.0001に設定~~
__やらない方がいいです。詳しくは[次の記事](https://qiita.com/_karukaru_/items/f7cdbffff591d0f0fb9b)へ。__
2018/2/3追記：
これ効果あるかわかんねぇな
ついでにRigitbodyのInterpolateも設定

HDMIキャプチャ画面（WebCamTexture）が表示されていない時だけメチャクチャ動作が軽かったので、もしかしたらそれが原因かも。
→WebCamTextureを張り付けたPlaneを無効化したら動作が軽くなったので、コイツが原因と断定。
多分キャプチャ時のフレームレートに足を引っ張られてた。
別の方法でキャプチャ画面を表示する方法を考える。

2018/2/6追記：
「RedererTextureがくそ重いからUnityCam使わずに直接ウィンドウキャプチャした方が（多分）パフォーマンス出る」という情報をいただきました。
https://twitter.com/toRisouP/status/960569019681062912
https://twitter.com/toRisouP/status/960569087691653121

# ゆかりさんとスプラトゥーン2　その1
2/2にニコ生で配信しました
<script type="application/javascript" src="https://embed.nicovideo.jp/watch/sm32679407/script?w=640&h=360"></script><noscript><a href="http://www.nicovideo.jp/watch/sm32679407">【VR配信】ゆかりさんとスプラトゥーン2【あーかいぶ】</a></noscript>

## 浮かび上がった問題点
実装・解決したものは打消し線を入れてます。

1. キャプチャ映像と音がズレている
1. カメラを持つとめっちゃブレる
1. 生放送の管理ができない
1. デスクトップの解像度が高すぎて文字が読めない
1. ~~「ピース」って言ったらピースしたい~~
1. ~~瞬きしたい~~
1. ~~モザイクで遊びたい~~
1. なんか移動がおかしい
1. ~~髪の毛をフワフワさせたい~~
1. 特定のコメントを受信したら色々なモデルを出現させたい  
例えば「ゾウ」と打ったらUnity内にゾウが出現するとか
1. ボタンを置いていろいろしたい  
カメラの切り替えとかオブジェクトの出現とか再配置とかゲーム画面をデスクトップ画面と置き換えるとか
1. HTC VIVE と Nintendo Switch のコントローラを同時に持つことが難しい  
腕にマジックテープなどでVIVEのコントローラを固定すれば何とかできるかも？
1. [ユニティちゃん Candy Rock Star ライブステージ！](https://www.youtube.com/watch?v=M_GUlXJ52YA)にあるスピーカー実装したい

# 瞬きしたい
[ココ](http://onoty3d.hatenablog.com/entry/2015/03/24/211247)を参考にして瞬きしたい
AutoBlinkは[Github](https://github.com/unity3d-jp/unitychan-crs/blob/master/Assets/UnityChan/Scripts/AutoBlink.cs)にある

SetBlendShapeWeightメソッド呼び出しの第1引数は、上に張ったモーフ一覧から瞬きモーフのインデックスを指定する。ゆかりさんなら0。
SkinnedMeshRendererはU_Char_1。

## 問題
1. 瞬きの回数が少ない気がする  
→ソースコードに瞬きのインターバルを指定する変数があったのでそこを見てみる  

```AutoBlink.cs
public float threshold = 0.3f;   // ランダム判定の閾値
public float interval = 3.0f;    // ランダム判定のインターバル
```

publicな変数なのでUnityのインスペクタから変更できますね。
![スクリーンショット (76).png](https://qiita-image-store.s3.amazonaws.com/0/213883/adaa91cb-5242-ffa7-443f-65b415f2229c.png)
Thresholdは、瞬きする確率の閾値。この値を0にすると必ず瞬きする。1にすると瞬きしなくなる。初期は0.3なので、瞬きを しない：する = 4:7 ぐらいの確率。
Intervalは、瞬きする判定を何秒ごとに行うか。初期は3秒ごと。

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ゆかりさんが瞬きするようになったよ <a href="https://t.co/Lmt6tyatG0">pic.twitter.com/Lmt6tyatG0</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/959691844035559425?ref_src=twsrc%5Etfw">2018年2月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# カメラの手ブレ補正
~~小数点第2位くらいを四捨五入すればいいんじゃない？~~
→小数点第4位を四捨五入したらいい感じになりました（なってない）

```ShakeCorrection.cs
using System;
using UnityEngine;

public class ShakeCorrection : MonoBehaviour
{
    //位置
    //補正をするか
    public bool posEnable = true;
    //どの桁以上を出力するか
    public int posThreshold = 1;

    //角度
    //補正をするか
    public bool rotEnable = true;
    //どの桁以上を出力するか
    public int rotThreshold = 1;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //位置を取得
        Vector3 pos = transform.position;
        //角度を取得
        Vector3 rot = transform.eulerAngles;

        //小数点第4位を四捨五入して、第3位までを出力する
        //位置
        if (posEnable)
        {
            pos.x = (float)Math.Round(pos.x, posThreshold, MidpointRounding.AwayFromZero);
            pos.y = (float)Math.Round(pos.y, posThreshold, MidpointRounding.AwayFromZero);
            pos.z = (float)Math.Round(pos.z, posThreshold, MidpointRounding.AwayFromZero);

            //四捨五入した結果を入力
            transform.position = pos;
        }


        //角度
        if (rotEnable)
        {
            rot.x = (float)Math.Round(rot.x, rotThreshold, MidpointRounding.AwayFromZero);
            rot.y = (float)Math.Round(rot.y, rotThreshold, MidpointRounding.AwayFromZero);
            rot.z = (float)Math.Round(rot.z, rotThreshold, MidpointRounding.AwayFromZero);

            //四捨五入した結果を入力
            transform.eulerAngles = rot;
        }
    }
}
```

角度の補正はこれでも十分なのですが（元々大きい値のため）、位置の補正は満足いく形にはなりませんでした。
閾値を2にすると移動がカクつくし、かといって3にするとあまり補正してくれないし。
なのでより精度を高めるべく、四捨五入ではなく二捨三入まで拡張することにしました。

```ShakeCorrection.cs
using System;
using UnityEngine;

public class ShakeCorrection : MonoBehaviour
{
    //位置
    //補正をするか
    public bool posEnable = true;
    //二捨三入にするか
    public bool posDetailedAccuracy = false;
    //どの桁以上を出力するか
    public int posThreshold = 1;

    //角度
    //補正をするか
    public bool rotEnable = true;
    //二捨三入にするか
    public bool rotDetailedAccuracy = false;
    //どの桁以上を出力するか
    public int rotThreshold = 1;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //位置を取得
        Vector3 pos = transform.position;
        //角度を取得
        Vector3 rot = transform.eulerAngles;

        //位置
        if (posEnable)
        {
            //二捨三入の準備
            if (posDetailedAccuracy)
            {
                pos *= 2;
            }

            //四捨五入
            pos.x = (float)Math.Round(pos.x, posThreshold, MidpointRounding.AwayFromZero);
            pos.y = (float)Math.Round(pos.y, posThreshold, MidpointRounding.AwayFromZero);
            pos.z = (float)Math.Round(pos.z, posThreshold, MidpointRounding.AwayFromZero);

            //二捨三入へ変換
            if (posDetailedAccuracy)
            {
                pos /= 2;
            }

            //結果を入力
            transform.position = pos;
        }


        //角度
        if (rotEnable)
        {
            //二捨三入の準備
            if (rotDetailedAccuracy)
            {
                rot *= 2;
            }

            //四捨五入
            rot.x = (float)Math.Round(rot.x, posThreshold, MidpointRounding.AwayFromZero);
            rot.y = (float)Math.Round(rot.y, posThreshold, MidpointRounding.AwayFromZero);
            rot.z = (float)Math.Round(rot.z, posThreshold, MidpointRounding.AwayFromZero);

            //二捨三入へ変換
            if (rotDetailedAccuracy)
            {
                rot /= 2;
            }

            //結果を入力
            transform.eulerAngles = rot;
        }
    }
}
```

## できたもの
### 位置補正のみ、四捨五入バージョン
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">カメラの手ブレ補正入れてみました <a href="https://t.co/C5rdSZ1F9l">pic.twitter.com/C5rdSZ1F9l</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/959703812448534528?ref_src=twsrc%5Etfw">2018年2月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### 位置・角度補正あり、二捨三入バージョン
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">手ブレ補正のプログラムをちょっと変えてみた <a href="https://t.co/hvvjQ8poQM">pic.twitter.com/hvvjQ8poQM</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/959745060270059520?ref_src=twsrc%5Etfw">2018年2月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

あまり効果がないような気がする。ここで悩んでても仕方ないので次行きます。

# 髪の毛をフワフワさせる
__SpringBoneが更新されてより使いやすくなりました。また各種調整も入ったためこれからSpringBoneを入れる方は新しいSpringBoneを使いましょう。詳しくは[次の記事](https://qiita.com/_karukaru_/items/f7cdbffff591d0f0fb9b)へ。__
[ココ](https://qiita.com/n0mimono/items/aff3b133680d409137e9)と[ココ](http://tsubakit1.hateblo.jp/entry/2014/11/08/233000)を参考にゆかりさんのもみあげとアホ毛をフワフワさせたい。ついでにスカート？ワンピース？やパーカーのうさみみフードも。
SpringManagerは[Github](https://github.com/unity3d-jp/unitychan-crs/blob/master/Assets/UnityChan/Scripts/SpringManager.cs)にある。
SpringBoneは[Github](https://github.com/unity3d-jp/unitychan-crs/blob/master/Assets/UnityChan/Scripts/SpringBone.cs)にある。
ってかめんどくさいから[unitychanのデータ](https://github.com/unity3d-jp/unitychan-crs)全部DLしよう。

## 上手なSpringColliderを付けるコツ
1. 体のSpringColliderの大きさと位置を決める
体のSpringColliderは大体3個くらいに分けて、ピラミッド型（もしくは若干のボンキュッボン型）になるように大きさを調整する。体に対して若干小さめにしたほうが自然な感じになる。
ただし頭は少しだけ大きくすることで髪の毛のフワフワ感が出る。
![スクリーンショット (79).png](https://qiita-image-store.s3.amazonaws.com/0/213883/c20d0faa-4273-7628-3ada-c1f502b188e3.png)
ここでモデルの状態を確認しても二度手間になるため調整してはいけない。後でまとめてやろう。

1. フワフワ対象のSpringBoneのRadiusを決める
**SpringBoneを付けるときは体の半分だけ付けて、調整が終わってから全身に貼り付けよう。**
髪の毛など細いSpringBoneはRadiusを小さくして、うさみみなどのある程度の大きさを持ったものは初期値のまま、など対象のIKの大きさによってRadiusを調整するととても自然な感じになってハッピーになれる。
![スクリーンショット (81).png](https://qiita-image-store.s3.amazonaws.com/0/213883/47ce230e-39d9-6d52-0863-b50edcb528a4.png)
![スクリーンショット (83).png](https://qiita-image-store.s3.amazonaws.com/0/213883/1e26561d-1f50-cd88-1b87-36a48d5fa805.png)
あほ毛とうさみみの比較。あほ毛はRadiusを小さくしてうさみみは初期値のままにしてある。

1. 不自然な個所の微調整
ここで初めて大きさや位置の調整を行う。
長いもみあげを持つキャラクター（結月ゆかりなど）は、肩のSpringColliderを作ってサイズを小さめに設定するといい感じになる。
ある程度調整を行ったがどうしても不自然な個所がある場合は、元々のIKに回転をかけることも考えること。
![スクリーンショット (80).png](https://qiita-image-store.s3.amazonaws.com/0/213883/2beb4366-2584-9016-7c0a-8a4cbcbb4ee0.png)
ゆかりさんのもみあげは少しだけ前に回転させている。

1. 必要ならSpringBoneのStiffness Forceの調整
Stiffness Forceは小さくすると無重力空間にいる状態みたくなる。大きくするとフサフサ感がなくなる。

正直この調整は妥協点を見つけないと無限に時間を持っていかれる。どこかで区切りをつけた方がよい。

## 問題
1. サンプルは動くけどVRコスプレのプロジェクトに適用しても動かない  
1つめの原因は[ココ](https://qiita.com/n0mimono/items/aff3b133680d409137e9)を見て解決。  
2つめの原因は謎。なんかSpringManagerとかSpringBoneとか設定メチャクチャ試したけど動かなかった。全部1回削除して再度適用しなおしたら動いた。謎。

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ゆかりさんのうさみみが揺れるようになったよ！<br>左は適用前、右が適用後 <a href="https://t.co/qFiSdoXmHa">pic.twitter.com/qFiSdoXmHa</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/960102915720462338?ref_src=twsrc%5Etfw">2018年2月4日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 特定のコメントを受信したら色々なモデルを出現させたい
## コメントをオブジェクトとして出現させる
[コレ](https://qiita.com/toRisouP/items/52c070147dcbdeb4b9df)を使ってやります！アンコちゃんからUnityへコメントを引っ張ってきて、コメントを3DモデルとしてUnity内に出現させたい。~~つまりみゅみゅさんのパクリ。~~
[Unityから直接ニコ生のAPIを叩く方法もある。](https://github.com/TORISOUP/UnityNicoliveClient)
文字の3Dモデルは[FlyingText3D](https://www.assetstore.unity3d.com/jp/#!/content/3627)というアセットを使って生成する。FlyingText3Dについては[ココ](https://qiita.com/arumani/items/fc7211287678deb75277)を参考に作業すれば問題ない。

1. フォントファイルがある場所を探す
とりあえずメイリオを使いたいので、Windows標準のフォント格納場所である `C:\Windows\Fonts\` にアクセス。メイリオの名前は "Meiryo UI"。

1. .ttcファイルを分解して.ttfファイルにする
[ココ](https://mndangler.net/2017/04/ttc_convert/)とか参考にすればいいんじゃないかな

1. 適当なGameObjectにFlyingTextをアタッチ
1. 同じオブジェクトに以下のようなスクリプトをアタッチ

```CreateText.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateText : MonoBehaviour
{
    int timer = 0;

    // Use this for initialization
    void Start()
    {

        //文字の指定
        var objectParent = FlyingText.GetObjects(timer.ToString()).transform;

        //位置の指定
        var pos = objectParent.position;
        pos.y = 1;
        pos.x = -10;
        objectParent.position = pos;


        //Rigidbodyの指定
        Rigidbody[] rigidBodies = objectParent.GetComponentsInChildren<Rigidbody>();

        foreach(Rigidbody rb in rigidBodies)
        {
            rb.mass = (float)0.001;
            rb.drag = 10;
        }

    }

    // Update is called once per frame
    void Update()
    {
        if (timer >= 100)
        {
            Start();
            timer = 0;
        }

        timer++;
    }
}
```

これで100フレームごとに3Dオブジェクトが出現する。
RigidbodyのMASSとかDRAGの値はこのくらいだといい感じにフワーっと落ちてくれる
Colliderの付け方は、インスペクタから "Add Rigidbody" の上にある "Collider Type" で適当なのを選べばいい。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">FlyingText3Dのデモ <a href="https://t.co/w7xpplWIbU">pic.twitter.com/w7xpplWIbU</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/960811518341165056?ref_src=twsrc%5Etfw">2018年2月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

これで動的にテキストの3Dオブジェクトを生成することができるようになりました。
NicoliveCommentRecieverからコメント受信時にCreateTextを呼び出してあげればOK。
Randomとかで出現位置を変えてあげればバラバラに落ちていい感じになるんじゃないかな。

### 問題
1. オブジェクトを掴んだ時に特定のイベントを呼び出したい  
[ココ](https://qiita.com/hiroyuki7/items/95c66aee26115cf24a19)とか[ココ](https://qiita.com/mo4_9/items/210be68edb241c6ae735)を参考にカスタマイズ。

```CreateObject.cs
//文字オブジェクトを作成
commentObject = FlyingText.GetObjects(comment.Message);
if (commentObject == null)
{
        return;
}

//文字オブジェクトのTransformを取得
commentObjectTransform = commentObject.transform;
if (commentObjectTransform == null)
{
        return;
}

/*** 中略 ***/

//掴めるように Grabbable 有効化
VRTK_InteractableObject[] InteractableObject = commentObjectTransform.GetComponentsInChildren<VRTK_InteractableObject>();
foreach (VRTK_InteractableObject io in InteractableObject)
{
        io.isGrabbable = true;
        io.enabled = true;

        //Rigidbodyを取得
        Rigidbody rb = io.transform.GetComponent<Rigidbody>();

        //掴んだ時にイベントを呼び出すよう設定
        io.InteractableObjectGrabbed += (sender, args) =>
        {
            if (rb.useGravity)
            {
                rb.useGravity = false;
                rb.isKinematic = true;

                //オブジェクトをDestroyするタイマーを止める
                var timer = io.transform.GetComponent<ObjectTimer>();
                timer.TimerStop();
            }
        };
}
```

1. Rigidbody の isKinematic を動的にTrueにできない  
上記のプログラムだと useGravity はFalseにできるけど、isKinematic をTrueにはできない。  
仕方ないので[FreezeAll](https://loumo.jp/wp/archive/20131210003026/)とかで代用。

```CreateObject.cs
        //Rigidbodyを取得
        Rigidbody rb = io.transform.GetComponent<Rigidbody>();

        //掴んだのをやめた時（手を離したとき）にイベントを呼び出すよう設定
        io.InteractableObjectUngrabbed += (sender, args) =>
        {
            //回転、位置を固定
            rb.constraints = RigidbodyConstraints.FreezeAll;
        };

        //掴んだ時にイベントを呼び出すよう設定
        io.InteractableObjectGrabbed += (sender, args) =>
        {
            if (rb.useGravity)
            {
                rb.useGravity = false;
                rb.isKinematic = true;

                //オブジェクトをDestroyするタイマーを止める
                var timer = io.transform.GetComponent<ObjectTimer>();
                timer.TimerStop();
            }

            //回転、位置をフリーに設定
            rb.constraints = RigidbodyConstraints.None;
        };
```

って思ってたけど、掴んでいる時にisKinematicをTrueにしてもVRTKに無効化されるよね・・・。
離した時にTrueにしないと意味ないよね。

```CreateObject.cs
        //Rigidbodyを取得
        Rigidbody rb = io.transform.GetComponent<Rigidbody>();

        //掴んだのをやめた時（手を離したとき）にイベントを呼び出すよう設定
        io.InteractableObjectUngrabbed += (sender, args) =>
        {
            if (!rb.isKinematic)
            {
                //Kinematicを有効化
                rb.isKinematic = true;
            }
        };

        //掴んだ時にイベントを呼び出すよう設定
        io.InteractableObjectGrabbed += (sender, args) =>
        {
            if (rb.useGravity)
            {
                //Gravityを無効化
                rb.useGravity = false;

                //オブジェクトをDestroyするタイマーを止める                    
                var timer = io.transform.GetComponent<ObjectTimer>();
                timer.TimerStop();
            }
        };
```
isKinematicの動的な有効化できました（迫真）。
深夜2時の脳みそは信用できませんね。眠くなったら素直に寝ましょう。

1. 文字オブジェクトがライティングの影響を受けて見えづらい  
後でシェーダーを動的に変更するプログラム追加します。

### できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ニコ生のコメントを取得して3Dオブジェクトとして生成するようにしました<br>（全然関係ない方のニコ生のコメント） <a href="https://t.co/Zqc8NlqAso">pic.twitter.com/Zqc8NlqAso</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/961186518994796545?ref_src=twsrc%5Etfw">2018年2月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# モザイクで遊びたい
[Unity AssetStoreまとめ](http://www.asset-sale.net/)さんを見ていたら[こんな記事](http://www.asset-sale.net/entry/free180202)を見つけたのでモザイクで遊んでみる。
記事の通りに行えば何も問題はない。
遠近感が失われるオブジェクトなのでどこまで手を伸ばしたら手に取れるのかわかりづらい。オブジェクトの厚さはそれなりにあった方がいいかも。

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">モザイクで遊んでみた <a href="https://t.co/xZLNXJd5bm">pic.twitter.com/xZLNXJd5bm</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/961183385115770881?ref_src=twsrc%5Etfw">2018年2月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# ピースしたい
キャラクターの指の制御には[MMD4FingerController](https://qiita.com/toRisouP/items/14fe62f89808013f9f6e#%E6%8C%87%E3%81%AE%E5%BD%A2)や[コレ](http://eyln.hatenablog.com/entry/2016/12/19/003435)などがありますが、個人的にわかり辛かった（ソースコード読む気がないだけ）のと大きくカスタマイズしたかったのでスクリプトを自作することにしました。
指の角度を変えるだけなのでそこまで難しいスクリプトにはなってないと思います。

```HandController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    //右手
    [Tooltip("右手 親指のTransform")]
    public GameObject rightThumbFinger;

    [Tooltip("右手 人差し指のTransform")]
    public GameObject rightIndexFinger;

    [Tooltip("右手 中指のTransform")]
    public GameObject rightMiddleFinger;

    [Tooltip("右手 薬指のTransform")]
    public GameObject rightRingFinger;

    [Tooltip("右手 小指のTransform")]
    public GameObject rightLittleFinger;

    //左手
    [Tooltip("左手 親指のTransform")]
    public GameObject leftThumbFinger;

    [Tooltip("左手 人差し指のTransform")]
    public GameObject leftIndexFinger;

    [Tooltip("左手 中指のTransform")]
    public GameObject leftMiddleFinger;

    [Tooltip("左手 薬指のTransform")]
    public GameObject leftRingFinger;

    [Tooltip("左手 小指のTransform")]
    public GameObject leftLittleFinger;

    //右手用リスト
    //List<Transform> right = new List<Transform>();
    private static List<List<GameObject>> rightHand = new List<List<GameObject>>();

    //左手用リスト
    //List<Transform> left = new List<Transform>();
    private static List<List<GameObject>> leftHand = new List<List<GameObject>>();

    // Use this for initialization
    void Start()
    {
        //右手を登録
        //0: 親指
        //1: 人差し指
        //2: 中指
        //3: 薬指
        //4: 小指
        rightHand.Add(GetAllChildren.GetAll(rightThumbFinger));
        rightHand.Add(GetAllChildren.GetAll(rightIndexFinger));
        rightHand.Add(GetAllChildren.GetAll(rightMiddleFinger));
        rightHand.Add(GetAllChildren.GetAll(rightRingFinger));
        rightHand.Add(GetAllChildren.GetAll(rightLittleFinger));

        //左手を登録
        //0: 親指
        //1: 人差し指
        //2: 中指
        //3: 薬指
        //4: 小指
        leftHand.Add(GetAllChildren.GetAll(leftThumbFinger));
        leftHand.Add(GetAllChildren.GetAll(leftIndexFinger));
        leftHand.Add(GetAllChildren.GetAll(leftMiddleFinger));
        leftHand.Add(GetAllChildren.GetAll(leftRingFinger));
        leftHand.Add(GetAllChildren.GetAll(leftLittleFinger));
    }

    public static int Rstate;
    public static bool RisGrab;
    public static int Lstate;
    public static bool LisGrab;
    private static int coef; 

    // Update is called once per frame
    void Update()
    {
        if (!RisGrab)
        {
            switch (Rstate)
            {
                case 0:
                    nomal(true);
                    break;
                case 1:
                    rock(true);
                    break;
                case 2:
                    peace(true);
                    break;
                case 3:
                    paper(true);
                    break;
                case 4:
                    cheat(true);
                    break;
                default:
                    Rstate = 0;
                    nomal(true);
                    break;
            }
        }

        if (!LisGrab)
        {
            switch (Lstate)
            {
                case 0:
                    nomal(false);
                    break;
                case 1:
                    rock(false);
                    break;
                case 2:
                    peace(false);
                    break;
                case 3:
                    paper(false);
                    break;
                case 4:
                    cheat(false);
                    break;
                default:
                    Lstate = 0;
                    nomal(false);
                    break;
            }
        }
    }

    //左右の手を判定して手のリストを返す関数
    private static List<List<GameObject>> getHand(bool isRight)
    {
        List<List<GameObject>> hand = new List<List<GameObject>>();
        if (isRight)
        {
            hand = rightHand;
            coef = 1;
        }
        else
        {
            hand = leftHand;
            coef = -1;
        }

        return hand;
    }

    //指を元に戻す
    public static List<List<GameObject>> nomal(bool isRight)
    {
        List<List<GameObject>> hand = getHand(isRight);

        //全ての指の回転をリセット
        for (int i = 0; i < 5; i++)
        {
            foreach (GameObject gameObject in hand[i])
            {
                gameObject.transform.localRotation = Quaternion.Euler(0, 0, 0);
            }
        }

        return hand;
    }

    //手をピースに
    public static void peace(bool isRight)
    {
        //指をリセット
        List<List<GameObject>> hand = nomal(isRight);

        //親指を回転
        hand[0][1].transform.localRotation = Quaternion.Euler(45, coef * 45, 0);
        hand[0][2].transform.localRotation = Quaternion.Euler(45, coef * 45, 0);

        //人差し指を回転
        hand[1][0].transform.localRotation = Quaternion.Euler(0, coef * -10, 0);

        //中指を回転
        hand[2][0].transform.localRotation = Quaternion.Euler(0, coef * 10, 0);

        //薬指と小指を回転
        for (int i = 3; i < 5; i++)
        {
            foreach (GameObject gameObject in hand[i])
            {
                gameObject.transform.localRotation = Quaternion.Euler(0, 0, coef * -75);
            }
        }
    }

    //手をグーに
    public static void rock(bool isRight)
    {
        //指をリセット
        List<List<GameObject>> hand = nomal(isRight);

        //親指を回転
        hand[0][1].transform.localRotation = Quaternion.Euler(45, coef * 45, 0);
        hand[0][2].transform.localRotation = Quaternion.Euler(45, coef * 45, 0);

        //その他の指を回転
        for (int i = 1; i < 5; i++)
        {
            foreach (GameObject gameObject in hand[i])
            {
                gameObject.transform.localRotation = Quaternion.Euler(0, 0, coef * -75);
            }
        }
    }

    //手をパーに
    public static void paper(bool isRight)
    {
        //指をリセット
        List<List<GameObject>> hand = nomal(isRight);

        //親指を回転
        hand[0][1].transform.localRotation = Quaternion.Euler(-10, coef * -10, 0);

        //人差し指を回転
        hand[1][0].transform.localRotation = Quaternion.Euler(0, coef * -10, 0);

        //薬指を回転
        hand[3][0].transform.localRotation = Quaternion.Euler(0, coef * 5, 0);

        //小指を回転
        hand[4][0].transform.localRotation = Quaternion.Euler(0, coef * 10, 0);
    }

    //手をグーチョキパー3つ混じったあの形に
    //http://www.irasutoya.com/2017/01/blog-post_2.html
    //フレミング左手の法則に似た形
    public static void cheat(bool isRight)
    {
        //指をリセット
        List<List<GameObject>> hand = nomal(isRight);

        //親指を回転
        hand[0][1].transform.localRotation = Quaternion.Euler(-10, coef * -10, 0);

        //人差し指を回転
        hand[1][0].transform.localRotation = Quaternion.Euler(0, coef * -10, 0);

        //中指を回転
        hand[2][0].transform.localRotation = Quaternion.Euler(0, coef * 10, 0);

        //薬指と小指を回転
        for (int i = 3; i < 5; i++)
        {
            foreach (GameObject gameObject in hand[i])
            {
                gameObject.transform.localRotation = Quaternion.Euler(0, 0, coef * -75);
            }
        }
    }

    //物を掴んている形に
    public static void grab(bool isRight)
    {
        //指をリセット
        List<List<GameObject>> hand = nomal(isRight);

        //親指を回転
        hand[0][0].transform.localRotation = Quaternion.Euler(0, 0, coef * -20);
        hand[0][1].transform.localRotation = Quaternion.Euler(20, coef * 20, 0);
        hand[0][2].transform.localRotation = Quaternion.Euler(20, coef * 20, 0);

        //その他の指を回転
        for (int i = 1; i < 5; i++)
        {
            foreach (GameObject gameObject in hand[i])
            {
                gameObject.transform.localRotation = Quaternion.Euler(0, 0, coef * -30);
            }
        }
    }
}
```

スクリプト中に出てくる GetAllChildren は、[コチラ](http://kazuooooo.hatenablog.com/entry/2015/08/07/010938)のスクリプトをちょっと改造（親のオブジェクトもリストの中に含めるように）したものです。

```GetAllChildren.cs
    public static List<GameObject> GetAll(this GameObject obj)
    {
        List<GameObject> allChildren = new List<GameObject>();
        allChildren.Add(obj);     //親オブジェクトもListに含めるようにAdd
        GetChildren(obj, ref allChildren);
        return allChildren;
    }
```

なんかすごい汚いプログラム書いてしまいました。Overrideとか駆使すればよかったですが今更面倒なのでこのままいきます。
指がアニメーションしないで一瞬で切り替わってしまいますが、とりあえず今はこれでいいでしょう。そのうちいい感じにします。
指のオブジェクトはそれぞれの根本にあるGameObjectを指定してください。ゆかりさんだとこんな感じ。
![HandController.png](https://qiita-image-store.s3.amazonaws.com/0/213883/d8bf418c-804b-9d4b-672e-0a3f6a882e6d.png)

## 指の可動域
ここで例に挙げているのは右手の指です。**左手の指は回転方向にマイナスをかけないとおかしくなります。**
人差し指から小指までは **Z軸を中心にマイナス方向へ回転** させれば自然な感じ。 **最大90度。** Y軸中心は **-10～+10度程度** なら回しても問題ない。**X軸はダメ。**
ただし注意が必要なのが親指で、 **X軸とY軸を同時にプラス方向へ回転させないとおかしくなる。** 回す角度は大体 **45度、最大60度くらいまで** にしないと回りすぎる。 **Z軸は回転しすぎるとあり得ない方向に回る。**
まとめると、

|   | 人差し指から小指 |     親指    |
|:-:|:--------------:|:-----------:|
| x |       x        | -10? ~ 45+α |
| y |    -10 ~ 10    | -10? ~ 45+α |
| z |    -90 ~ 0     | -20? ~ 10?  |

![DVcDx8-UQAEnkUC.jpg](https://qiita-image-store.s3.amazonaws.com/0/213883/584cfea4-9b12-2f6d-aee7-a3b5fbcc5750.jpeg)
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">軽くピースさせてみたらいい感じになった <a href="https://t.co/GkQyAjAySm">pic.twitter.com/GkQyAjAySm</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/961246972639248384?ref_src=twsrc%5Etfw">2018年2月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## できたもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">指を変えられるようになったよ <a href="https://t.co/gQvgoEFAHO">pic.twitter.com/gQvgoEFAHO</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/961321141863292928?ref_src=twsrc%5Etfw">2018年2月7日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# [UnityNicoliveClient](https://github.com/TORISOUP/UnityNicoliveClient)
とりすーぷさん作。
アセット内で使用されているSendWebRequestがUnity2017.2以上じゃないと実装されていないので、Unityのバージョンを再度変更。
[Microphoneの問題](https://issuetracker.unity3d.com/issues/microphone-dot-start-is-not-recording-the-audio-from-selected-recording-device)もあるので[Unity2017.2.0b10](https://unity3d.com/jp/unity/beta/unity2017.2.0b10)をインストール。Fixed in future releaseってなってたけど、どのバージョンで修正してくれるんだろう。
無理にこれを使う必要はないので、Unity側で修正されたら移行する形でもよさそう。

2/12追記：とりすーぷさん側で2017.1以下でも動くように対応していただきました。ありがとうございます。
実装については[別記事にて。](https://qiita.com/_karukaru_/items/f7cdbffff591d0f0fb9b)

# ゆかりさんとスプラトゥーン2　その2
2/10にニコ生で配信しました
<script type="application/javascript" src="https://embed.nicovideo.jp/watch/sm32719232/script?w=640&h=360"></script><noscript><a href="http://www.nicovideo.jp/watch/sm32719232">【VR配信】ゆかりさんとスプラトゥーン2　その2【あーかいぶ】</a></noscript>

## 浮かび上がった問題点
実装・解決したものは打消し線を入れてます。

### 既存の問題
1. キャプチャ映像と音がズレている
1. カメラを持つとブレる
1. 生放送の管理ができない
1. デスクトップの解像度が高すぎて文字が読めない
1. VRIKの再セットアップしたい
1. 特定のコメントを受信したら色々なモデルを出現させたい  
例えば「ゾウ」と打ったらUnity内にゾウが出現するとか
1. ボタンを置いていろいろしたい  
カメラの切り替えとかオブジェクトの出現とか再配置とかゲーム画面をデスクトップ画面と置き換えるとか
1. HTC VIVE と Nintendo Switch のコントローラを同時に持つことが難しい  
腕にマジックテープなどでVIVEのコントローラを固定すれば何とかできるかも？
1. [ユニティちゃん Candy Rock Star ライブステージ！](https://www.youtube.com/watch?v=M_GUlXJ52YA)にあるスピーカー実装したい

### 新たな問題
1. ゲーム画面を見ている時にゆかりさんが後ろを向いている  
鏡の設置や配信設定を変えることで対応可能？
1. やっぱりWebCamTexture重い
1. コメントを落とす範囲が広すぎて取りに行けない
1. テレポート時に姿勢がおかしくなってる
1. PCゲーム配信したい
1. 配信用のオブジェクト配置にするのが面倒
1. ~~Gameウィンドウからフォーカスが外れた時にフレームレートが落ちる~~
1. ~~マルチプレイしたい~~
1. 車のモデル置いてレースクイーンごっことかしたい
1. ~~表情を変化させたい~~

# Gameウィンドウからフォーカスが外れた時にフレームレートが落ちる
以前からGameウィンドウを最前面に表示していないとフレームレートが落ちる問題を解決できずにいました。
そんな時に以下の情報を頂きました。ありがとうございます。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">もしOVRLipSyncを使っているのなら、focusedフラグを使った処理をあらかたコメントアウトすることで対応できます<br>もし違えば、Profilerビューで、モードをdetailにしてテストすると、どこが重いのか確認できます</p>&mdash; inew (@Alupaca1363Inew) <a href="https://twitter.com/Alupaca1363Inew/status/962539359940632577?ref_src=twsrc%5Etfw">2018年2月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

試しにOVRLipSync関連のスクリプトを無効化したところ問題が解決したため、focusedフラグを使用している部分を無効化することにしました。
focusedフラグを使用しているのは **OVRLipSyncMicInput.cs** で、処理内容は **フォーカスが外れた時にマイク入力を破棄する** 内容でした。
アプリケーションの終了時だけマイクを破棄してくれればいいので、容赦なくコメントアウトします。

# マルチプレイしたい
[Photon Unity Networking](https://www.assetstore.unity3d.com/jp/#!/content/1786)を使ってマルチプレイしたい。
[このシリーズ](http://tech.pjin.jp/blog/2016/07/20/unity_pun_1/)を見れば基本的なことはわかる。
[コールバックメソッドの一覧はこちら。](http://naichilab.blogspot.com/2014/12/unitypunphoton-unity-networking.html)

## 共通部
実装した方がいい機能

1. マスターでログインするかゲストでログインするかで処理を変えた方がいい
1. マスターからゲストを強制ログアウトさせる機能を実装した方がいい
1. Roomを作れるのはマスターだけにした方がいい

基本的な枠組みは[コチラ](http://tech.pjin.jp/blog/2016/07/20/unity_pun_1/)から拝借。
このスクリプトを適当な空オブジェクトにアタッチしてあげればいい。

```Login.cs
using UnityEngine;
using System.Collections;

public class Login : MonoBehaviour
{
    public bool isMaster;
    public static string userName;
    private string roomName = "ゆかりさんと○○";

    void Start()
    {
        if (!isMaster)
        {            
            //ゲストには無関係な機能を無効化
            GameObject.Find("VRTK").SetActive(false);
            GameObject yukariObject = GameObject.Find("結月ゆかり_純_ver1.0");
            yukariObject.GetComponent<RootMotion.FinalIK.VRIK>().enabled = false;
            yukariObject.GetComponent<OVRLipSyncContextMorphTarget>().enabled = false;
            yukariObject.GetComponent<OVRLipSyncContext>().enabled = false;
            yukariObject.GetComponent<OVRLipSyncMicInput>().enabled = false;
            yukariObject.GetComponent<UnityChan.AutoBlink>().enabled = false;
            yukariObject.GetComponent<UnityChan.SpringManager>().enabled = false;
            yukariObject.GetComponent<HandController>().enabled = false;
            GameObject.Find("uDD_Board").SetActive(false);
            GameObject.Find("FilmCamera/Camera").GetComponent<UnityCam>().enabled = false;
            GameObject.Find("NicoLiveComment").SetActive(false);
            GameObject.Find("CreateObject").SetActive(false);
        }

        // Photonに接続する(引数でゲームのバージョンを指定できる)
        PhotonNetwork.ConnectUsingSettings(null);
    }

    // ロビーに入ると呼ばれる
    void OnJoinedLobby()
    {
        Debug.Log("ロビーに入りました。");
        //YukariPhoton.setPhoton();

        if (isMaster)
        {
            //ルームを作成
            PhotonNetwork.CreateRoom(roomName, null, null);
        }
        else
        {
            // ルームに入室
            PhotonNetwork.JoinRoom(roomName);
        }


    }

    // ルームに入室すると呼ばれる
    void OnJoinedRoom()
    {
        Debug.Log("ルームへ入室しました。");

        if (isMaster)
        {
            //マスター側

            //ユーザ名を設定
            userName = "Master";
            //ユーザ名を通知
            PhotonNetwork.playerName = userName;
        }
        else
        {
            //ゲスト側

            //ユーザ名の設定
            if (userName != null)
            {
                //ユーザ名を通知
                PhotonNetwork.playerName = userName;
            }
            else
            {
                //プレイヤーの数を取得
                int num = PhotonNetwork.otherPlayers.Length;
                //ユーザ名を生成
                userName = "Guest" + num;
                //ユーザ名を通知
                PhotonNetwork.playerName = userName;
            }

            //ゲストのカメラを生成
            createCamera();
        }
    }

    // ルームの入室に失敗すると呼ばれる
    void OnPhotonJoinRoomFailed()
    {
        Debug.Log("ルームの入室に失敗しました。");

        //マスターだけがRoomを作る
        if (isMaster)
        {
            //ないとは思うけど、もう一度Roomを作る
            PhotonNetwork.CreateRoom(roomName);
        }
        else
        {
            Debug.Log("再接続");
            // ルームに入室する
            PhotonNetwork.JoinRoom(roomName);
        }

    }

    //ゲストのカメラを生成する関数
    private void createCamera()
    {
        Vector3 pos = new Vector3(0, (float)1.2, (float)-1.5);

        // 第1引数にResourcesフォルダの中にあるプレハブの名前(文字列)
        // 第2引数にposition
        // 第3引数にrotation
        // 第4引数にView ID(指定しない場合は0)
        GameObject photonObject = PhotonNetwork.Instantiate("CubeCamera", pos, Quaternion.identity, 0);

        //photonObjectの名前をユーザ名に設定
        photonObject.transform.name = userName;

        //カメラを有効化
        photonObject.GetComponentInChildren<Camera>().enabled = true;
        //オーディオリスナーを有効化
        photonObject.GetComponentInChildren<AudioListener>().enabled = true;
    }
}
```

## マスター側
ゆかりさんの同期をするためには、VRIKの各IKに対してPhotonTransformViewを設定。SpringBoneにも付けないとフワフワしないけど、数が多いので断念。
もしやるならゆかりさんのIK全てに動的にスクリプトを紐づけた方がいい。
PhotonObjectのLoginスクリプトのIsMasterのチェックを付けてRun。
結月ゆかりモデルをプレハブ化していないけどいいの？と思うが、プレハブ化して～のくだりは動的にオブジェクトを生成する場合にのみ必要。最初から存在するオブジェクトはそのままでいい。
[ココ](http://edom18.hateblo.jp/entry/2017/03/01/090602)とか参考になるかも。

## ゲスト側
ゲストユーザ向けにビルドするときの設定がややこしい。
[ココ](http://tsubakit1.hateblo.jp/entry/2014/12/24/233000)を参考にゲーム起動時の解像度設定画面を表示するように。SteamVRが文句言ってくるけど無視。
PhotonObjectのLoginスクリプトのIsMasterのチェックを外して、Project SettingのQualityをGood以上に設定。これをしないと光源が一部削除される。

ゲストユーザの接続の前にワンクッション置きたかったため、簡単なログインページみたいなのを作ってみました。
![スクリーンショット (93).png](https://qiita-image-store.s3.amazonaws.com/0/213883/607fd445-9541-97e6-89d5-5e3751120a3c.png)
タイトル画面の作り方は[ココ](https://gametukurikata.com/ui/startbuttonui)を、InputFieldから情報を抜き出すのは[ココ](http://tech.pjin.jp/blog/2016/09/27/unity_skill_4/)を参考にしました。
今はplayerNameに設定しているだけですが、ゲストの頭の上に名前とか表示出来たらよさそうですね。

```GuestTitleScript.cs
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GuestTitleScript : MonoBehaviour
{
    InputField inputField;

    void Start()
    {
        //InputFieldを取得
        GameObject go = GameObject.Find("InputField");
        inputField = go.GetComponent<InputField>();
    }

    //ボタンを押したら実行する
    public void GuestStart()
    {
        //Login.isMaster = false;
        Login.userName = inputField.text;
        SceneManager.LoadScene("VR Cosplay 201713");
    }
}
```

カメラの移動にはワールド座標系を用いれば難しくない。
[ココ](https://qiita.com/mokemokechicken/items/137c3a00d44841d3473e#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E7%B7%A8%E9%9B%86)と[ココ](https://qiita.com/yando/items/c406690c9ad87ecfc8e5)を参考に以下のスクリプトを作成。

```ControllCamera.cs
using UnityEngine;

public class ControllCamera : MonoBehaviour
{
    GameObject go;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (go == null)
        {
            go = GameObject.Find(Login.userName);
        }

        //スペースを押したときのイベント
        if (Input.GetKey(KeyCode.E))
        {
            Vector3 vector = go.transform.up * (float)0.025;
            go.transform.position += vector;
        }

        //Qを押したときのイベント
        if (Input.GetKey(KeyCode.Q))
        {
            Vector3 vector = go.transform.up * (float)0.025;
            go.transform.position -= vector;
        }

        //Wを押したときのイベント
        if (Input.GetKey(KeyCode.W))
        {
            //前へ進む
            Vector3 vector = go.transform.forward * (float)0.025;
            go.transform.position += vector;
        }

        //Aを押したときのイベント
        if (Input.GetKey(KeyCode.A))
        {
            //右へ進む
            Vector3 vector = go.transform.right * (float)0.025;
            go.transform.position -= vector;
        }

        //Sを押したときのイベント
        if (Input.GetKey(KeyCode.S))
        {
            //後ろへ進む
            Vector3 vector = go.transform.forward * (float)0.025;
            go.transform.position -= vector;
        }

        //Dを押したときのイベント
        if (Input.GetKey(KeyCode.D))
        {
            //左へ進む
            Vector3 vector = go.transform.right * (float)0.025;
            go.transform.position += vector;
        }

        //Rを押したときのイベント
        if (Input.GetKey(KeyCode.R))
        {
            //視点回転をリセット
            go.transform.rotation = Quaternion.Euler(0, 0, 0);
        }

        //↑を押したときのイベント
        if (Input.GetKey(KeyCode.UpArrow))
        {
            transform.Rotate(-1, 0, 0);
        }

        //↓を押したときのイベント
        if (Input.GetKey(KeyCode.DownArrow))
        {
            transform.Rotate(1, 0, 0);
        }

        //←を押したときのイベント
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            //左へ回転
            transform.Rotate(0, -1, 0);
        }

        //→を押したときのイベント
        if (Input.GetKey(KeyCode.RightArrow))
        {
            //右へ回転
            transform.Rotate(0, 1, 0);
        }
    }
}
```

ゲストを強制ログアウトさせる機能は[ココ](http://tech.pjin.jp/blog/2016/09/25/unity_pun_5/)と[ココ](http://www.urablog.xyz/entry/2016/09/21/223002)を参考に、上記スクリプトに以下を追加

```GuestController.cs
    //クライアント終了を通知する関数
    public static void SendEnd()
    {
        photonView.RPC("AppEnd", PhotonTargets.Others);
    }

    //クライアント終了処理
    [PunRPC]
    void AppEnd()
    {
        Application.Quit();
    }
```

あとはマスター側でSendEnd関数を呼べばいい。


## 問題
1. StartでInstantiateしたら ``Failed to Instantiate prefab: CameraCube. Client should be in a room. Current connectionStateDetailed: PeerCreated`` って言われた
RoomにJoinする前にInstantiateしようとしているのが問題。Joinした後に生成すれば問題ない。

1. 別のユーザのカメラが表示されてしまう
プレハブに登録したオブジェクトのCameraコンポーネントを無効化しておき、ログインしたタイミングでそのユーザが持っているカメラオブジェクトのCameraコンポーネントだけを有効化すれば問題ない。

```Login.cs
    //ゲストのカメラを生成する関数
    private void createCamera()
    {
        Vector3 pos = new Vector3(0, (float)1.2, (float)-1.5);

        // 第1引数にResourcesフォルダの中にあるプレハブの名前(文字列)
        // 第2引数にposition
        // 第3引数にrotation
        // 第4引数にView ID(指定しない場合は0)
        GameObject photonObject = PhotonNetwork.Instantiate("CubeCamera", pos, Quaternion.identity, 0);

        //photonObjectの名前をユーザ名に設定
        photonObject.transform.name = userName;

        //カメラを有効化
        //ここが重要！！！
        photonObject.GetComponentInChildren<Camera>().enabled = true;
    }
```

1. ゆかりさんに動的にPotonViewとかPhotonTransformViewとかAddComponentしてもPhotonViewに情報渡すときに``null reference exception``と怒られる  
ゆかりさんのモデルにPhoton関連のスクリプトを何もアタッチせず、動的にすべてのIKに対してPhoton関連をアタッチしようと考えて以下のスクリプトを書きました。

```YukariPhoton.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class YukariPhoton : MonoBehaviour
{
    // Use this for initialization
    void Start()
    {
        GameObject yukariObject = GameObject.Find("結月ゆかり_純_ver1.0/182.!Root");

        foreach (GameObject go in GetAllChildren.GetAll(yukariObject))
        {
            var photonView = go.GetComponent<PhotonView>();
            var photonTransformView = go.GetComponent<PhotonTransformView>();

            //位置を同期
            photonTransformView.m_PositionModel.SynchronizeEnabled = true;
            //回転を同期
            photonTransformView.m_RotationModel.SynchronizeEnabled = true;
            //Transformを同期するように設定
            photonView.ObservedComponents[0] = photonTransformView;
            //同期タイミングの設定
            photonView.synchronization = ViewSynchronization.UnreliableOnChange;
        }
    }
}
```

するとObservedComponents[0]に値を代入してるところで``null reference exception``とエラーを吐かれてしまいました。
いろいろ調べたところ、[ここに書いてあること](http://tsubakit1.hateblo.jp/entry/20140531/1401531248)が起きていたっぽい。
詳しく説明すると、変数の生成タイミングの違いによって起こっていたエラー。
FoldoutされているList型の変数が生成されるタイミングは、手動でAddComponentしたときは即座に、動的にAddComponentしたときはインスペクタで該当するGameObjectを見たら生成される。
Runした直後にアクセスしようとしてもインスペクタでGameObjectを見てないから変数が生成されていない。よってエラーが吐かれる。
正直この仕様考えたやつに対して~~ぶん殴りたい。~~ ~~小一時間問い詰めたい。~~ もしかしたら初期化すればいいのでは？[ココ](https://qiita.com/su10/items/1fa754572517456b04d7)とか参考になる。

対策としては事前に手動でコンポーネントをAddしておけばOK。右矢印とかShiftとか駆使してモデルのIKを全選択しよう。
ただし文字オブジェクトをどうやって同期させるかが非常に悩ましいことになった。
掴んだ時にイベントを発行してチャットでTransformの情報を送ってやる方法を考え中。

1. マスター側でゲストのカメラを移動させても同期しない  
ゲストのカメラにVRTK関連を付けて持ち運べるようにしているのだが、移動がゲスト側に同期されない。対策案を検討中。

1. なぜかゲストクライアントのCPU負荷がめちゃくちゃ高い
[ココ](https://qiita.com/flankids/items/aaef052162dacaef3dc2)とか参考になるかも。

## できたもの
[バーチャルアイドルちゃんねる](http://com.nicovideo.jp/community/co2555627)の生主の方々にお手伝いいただきテストプレイをしました。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">バーチャルアイドルちゃんねるの生主の方々に手伝っていただいて、ゆかりさんとテストプレイ（意味深）しました。<br>その1 <a href="https://t.co/9ziFhU3E22">pic.twitter.com/9ziFhU3E22</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/962747964752973824?ref_src=twsrc%5Etfw">2018年2月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">その2 <a href="https://t.co/P1mhEqba83">pic.twitter.com/P1mhEqba83</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/962747966829182976?ref_src=twsrc%5Etfw">2018年2月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">その3 <a href="https://t.co/Qv47ZUe2IU">pic.twitter.com/Qv47ZUe2IU</a></p>&mdash; karukaru＠VTuberハッカソン→AMGモータース (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/962747968515227648?ref_src=twsrc%5Etfw">2018年2月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 表情を変化させたい
[ココ](http://yasu9780.hatenablog.com/entry/2015/05/26/135121)とかAutoBlink.csとかを参考にして以下のスクリプトを書きました。
指を動かすスクリプトと似たようなものです。関数にPunRPCを付けて表情が変化したときにゲスト全員に変更通知が行くようにしています。

```FaceController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FaceController : MonoBehaviour
{
    //表情モーフの参照
    public SkinnedMeshRenderer skinnedMeshRenderer;

    //表情ステータス保存用変数
    public static int state;
    //表情変化フラグ
    public static bool flag;

    private PhotonView photonView;
    private UnityChan.AutoBlink autoBlink;

    // Use this for initialization
    void Start()
    {
        photonView = GetComponent<PhotonView>();
        autoBlink = GetComponent<UnityChan.AutoBlink>();
    }

    // Update is called once per frame
    void Update()
    {
        if (flag)
        {
            switch (state)
            {
                case 0:
                    photonView.RPC("setNomal", PhotonTargets.All, true);
                    break;
                case 1:
                    photonView.RPC("setSmile", PhotonTargets.All);
                    break;
                case 2:
                    photonView.RPC("setTereSmile", PhotonTargets.All);
                    break;
                case 3:
                    photonView.RPC("setLeftWink", PhotonTargets.All);
                    break;
                case 4:
                    photonView.RPC("setRightWink", PhotonTargets.All);
                    break;
                default:
                    photonView.RPC("setNomal", PhotonTargets.All, true);
                    break;
            }
        }

        flag = false;
    }

    //無表情へ
    [PunRPC]
    void setNomal(bool blinkAcrive)
    {
        skinnedMeshRenderer.SetBlendShapeWeight(1, 0);
        skinnedMeshRenderer.SetBlendShapeWeight(2, 0);
        skinnedMeshRenderer.SetBlendShapeWeight(4, 0);
        skinnedMeshRenderer.SetBlendShapeWeight(41, 0);
        autoBlink.isActive = blinkAcrive;
    }

    //笑顔へ
    [PunRPC]
    void setSmile()
    {
        setNomal(false);
        skinnedMeshRenderer.SetBlendShapeWeight(1, 100);
    }

    //照れ笑顔へ
    [PunRPC]
    void setTereSmile()
    {
        setNomal(false);
        skinnedMeshRenderer.SetBlendShapeWeight(1, 100);
        skinnedMeshRenderer.SetBlendShapeWeight(41, 100);
    }

    //左目ウインクへ
    [PunRPC]
    void setLeftWink()
    {
        setNomal(false);
        skinnedMeshRenderer.SetBlendShapeWeight(4, 100);
    }

    //右目ウインクへ
    [PunRPC]
    void setRightWink()
    {
        setNomal(false);
        skinnedMeshRenderer.SetBlendShapeWeight(2, 100);
    }
}
```

# これ以降の更新は別記事で行います
初期の段階からずっと同じ記事に書き続けていましたが、横の見出し一覧が4Kでもスクロール必須になったり、記事の保存が重かったり、編集したい箇所の把握が難しくなってきたりしているので、これ以降の更新は別記事に書きたいと思います。
[新しい記事はコチラです。](https://qiita.com/_karukaru_/items/f7cdbffff591d0f0fb9b)