100%自分用に書いたので記事内容のブラッシュアップをしていません。
MMD4Mecanim
MMD用モデルをUnityで扱えるように変換してくるアセット(?)
問題
- 変換されたモデルデータはMMDモデルと同じ場所に置かないとテクスチャが読み込まれない
(同じ場所というかMMD4Mecanimで指定されたデフォルトフォルダ)
UnityCam
Unity内のCameraにアタッチするとその映像をWebカメラのように扱える
問題
- なぜかサンプルが動作しない
→昔レジストリを弄りすぎて正常に登録できなかった?OS再インストールで解決
使い方(普通のプロジェクト)
-
RunMe First
をやる -
UnityCam-master\UnitySample
をプロジェクトのAssetsフォルダにぶち込む -
UnitySample\Assets\UnityCam\Scripts
にあるUnityCam.cs
を任意のCameraにアタッチ - プロジェクトをRunしてOBSなどで映像を取得する
使い方(VRプロジェクト)
- Canvasを配置して子に
UI -> Raw Image
を置く - これを無効化してGameウィンドウに表示されないようにする
- Assetsフォルダ以下にRender Textureを作成してSizeを指定する
- Raw ImageにRender Textureをアタッチ
使い方(ゲーム内で見る方法)
- Planeを作成する
- マテリアルのところにRaw Textureをアタッチ
ココを参考にしました
注意
-
SteamVR -> CameraRig -> Camera(head)
の子としてUnityCamをアタッチしたCameraを配置するとプレイヤー視点の映像が得られる。ただしフレームレートが落ちてVR酔いする。
→素直にGameウィンドウをキャプチャすることでこの問題を回避
できたもの
V R カ メ コ pic.twitter.com/dZaR3Y6SiJ
— karukaru@次はAMGモータース (@_karukaru_) 2018年1月26日
WebCamTexture
Webカメラなどの外部から入力された映像をテクスチャとして貼り付けられる
HDMIキャプチャからゲームの映像を取得したい
ココとか参考にすればいいんじゃないかな
コッチのほうが詳しい
問題
できたもの
バーチャル空間でスプラトゥーン pic.twitter.com/VGAQwgYcQz
— karukaru@次はAMGモータース (@_karukaru_) 2018年1月28日
Microphone
音声入力デバイスをUnity内で鳴らす
ココの最初を参考にしてゲーム音声をUnity内で流したい
問題
- なぜかデバイスの指定ができない(ちなUnity2017.3.0f3)
Microphone.devicesの[0]番目しか指定できない。デバイス名による指定(Microphone.devices[1]とか)をしても反映されない。nullでもデフォルトデバイスに指定されない。
→デバイス名を変更することで暫定的な解決
配列に入るデバイスは名前順というわけでもない。デバイス名の先頭に「あ」と入れておけばとりあえず先頭に来る。
配列の先頭はデフォルトデバイス。それ以降はマジで謎な並び。
素直に諦めてキャプチャ音を直で聞いたほうがよさそう
2018/2/2追記:UnityのIssue Trackerを見てみたらバグ報告されてました。のでUnity側の修正待ちです。暫定的にバージョンを落としてもいいかも。
→バージョンを2017.1.3f1に下げることで暫定的な対応
できたもの
ないです
AssetStore
古いUnityバージョンで作られたアセットは動作しない可能性が高い
特にUnity5.0以前は注意?
Final IK(VRIK)
買う 買いました(2018/1/31)
UnityでIKを扱うのにメッチャ便利なアセット
基本的にはココと同じことをすれば問題ない
Solverにある各値の説明
問題
-
MMDモデルに対してVRIKをアタッチしてもReferencesを自動で設定してくれない
→モデルのAnimation TypeをHumanoidにすることで解決
参考:そのいち -
ワープしたときにワープ前の座標に足座標を置いてくる
こんな感じ
→ワープしたときのイベントで足座標を移動してあげればよさそう(まだやってない)
→ココに解決策載ってた
→VRIKの設定がおかしいのが原因だった
各足の Position Weight を0にしないと原点に足が持ってかれる
(ただし追加のトラッカーなどで足の座標を取得できるなら1にする)
-
モデルと自分の体格が合わない
→モデルのScaleを少しだけ大きくすることで対応。Scaleを変えたら必ずVRIKの設定を見直すこと。参考
VRIKの設定を弄ることで大きく改善することがあるため、面倒でもこの設定は拘ろう。
また各種ターゲットの位置を微調整することでも改善する可能性がある。
リップシンクしたい
VOICEROIDの音声に反応してリアルタイムで口が動いたら喋ってる感あっていいよね
OVRLipSyncを使いたい、のだけど名前が変わった? 今(2018/2/1)は Oculus LipSync Unity というらしい。
でもプロジェクトにインポートしたらOVRLipSyncと表示された。お前の名前どっちだよ。
ココとコレを参考に進めれば問題ない。
ちなみに公式の結月ゆかりMMDモデルのモーフはこんな感じ
Runするごとに音声入力デバイスを指定するのは面倒なので、デバイス名を固定にしてプログラム側で対象を探すようにちょっと改造しました。
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();
}
}
問題
- Microphoneのデバイスが指定できないからVOICEROIDのリップシンクができない
→調べたらUnityのバグでした。修正待ち。詳しくはIssue Trackerへ。
→バージョンを2017.1.3f1に下げることで暫定的な対応
AssetStoreで買ったカメラのモデル
UnityCamの位置を示す用に使う
問題
- コントローラでつかめない
→Colliderが入ってなかった。Meshはあるので各パーツにMesh Colliderをアタッチして対応するメッシュを指定すればよい。
→モノによってはStaticがオンになっている。オフにしたら動かせる。
コントローラをUnityCamに映したくない
ココを参考にコントローラのモデルのレイヤーを「Controller」と設定し、UnityCamのCullingMaskでControllerを除外すればOK。
できたもの
CameraとコントローラモデルのLayerを弄ったら、特定のカメラだけコントローラを非表示にすることができた pic.twitter.com/cTNdk7G54a
— karukaru@次はAMGモータース (@_karukaru_) 2018年1月31日
CM3D2のメニューボタンのように、押したらコントローラの役割が変わるようにしたい
具体的には、テレポートモード、手変化モード、表情変化モードの3つを実装したい
また役割が変わったらわかりやすいようにコントローラのちょっと上あたりに通知を出したい
役割を切り替えるのは、対象がコンポーネントならコッチ、オブジェクトならコッチ
通知を出す方法はココが参考になる
問題
-
コントローライベントなどを追加したオブジェクトを取得できない
VRTKというGameObject直下にLeftCとRightCというコントローライベントなどをアタッチしたオブジェクトがあるのだが、これをスクリプトで外部からFindとかGetComponentできなかった。
なぜかというと、Runした状態だとVRTKによりLeftCとRightCが[Camera Rig]の各Controller配下に移動していたから。
見る場所を間違えていたため取得できていなかった。 -
左手の指が逆パカされてしまう
右手の指の回転軸で左手の指も回転していたため、回転方向がおかしくなってしまった。マイナスをかけることで修正。
できたもの
メニュー実装してテレポートモードと指を動かすモード作った pic.twitter.com/trc5bEdQkN
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月8日
重い処理をしているわけではないのにフレームレートがガタ落ちしている
腕やオブジェクトを動かすと、滑らかに移動しないで若干ワープしながら移動する。
視点を揺らすと瞬間移動しているのがよくわかる。
ココを参考にFixedUpdateのFixed Timestepを0.0001に設定
やらない方がいいです。詳しくは次の記事へ。
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にニコ生で配信しました
浮かび上がった問題点
実装・解決したものは打消し線を入れてます。
- キャプチャ映像と音がズレている
- カメラを持つとめっちゃブレる
- 生放送の管理ができない
- デスクトップの解像度が高すぎて文字が読めない
「ピース」って言ったらピースしたい瞬きしたいモザイクで遊びたい- なんか移動がおかしい
髪の毛をフワフワさせたい- 特定のコメントを受信したら色々なモデルを出現させたい
例えば「ゾウ」と打ったらUnity内にゾウが出現するとか - ボタンを置いていろいろしたい
カメラの切り替えとかオブジェクトの出現とか再配置とかゲーム画面をデスクトップ画面と置き換えるとか - HTC VIVE と Nintendo Switch のコントローラを同時に持つことが難しい
腕にマジックテープなどでVIVEのコントローラを固定すれば何とかできるかも? - ユニティちゃん Candy Rock Star ライブステージ!にあるスピーカー実装したい
瞬きしたい
ココを参考にして瞬きしたい
AutoBlinkはGithubにある
SetBlendShapeWeightメソッド呼び出しの第1引数は、上に張ったモーフ一覧から瞬きモーフのインデックスを指定する。ゆかりさんなら0。
SkinnedMeshRendererはU_Char_1。
問題
- 瞬きの回数が少ない気がする
→ソースコードに瞬きのインターバルを指定する変数があったのでそこを見てみる
public float threshold = 0.3f; // ランダム判定の閾値
public float interval = 3.0f; // ランダム判定のインターバル
publicな変数なのでUnityのインスペクタから変更できますね。
Thresholdは、瞬きする確率の閾値。この値を0にすると必ず瞬きする。1にすると瞬きしなくなる。初期は0.3なので、瞬きを しない:する = 4:7 ぐらいの確率。
Intervalは、瞬きする判定を何秒ごとに行うか。初期は3秒ごと。
できたもの
ゆかりさんが瞬きするようになったよ pic.twitter.com/Lmt6tyatG0
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月3日
カメラの手ブレ補正
小数点第2位くらいを四捨五入すればいいんじゃない?
→小数点第4位を四捨五入したらいい感じになりました(なってない)
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にするとあまり補正してくれないし。
なのでより精度を高めるべく、四捨五入ではなく二捨三入まで拡張することにしました。
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;
}
}
}
できたもの
位置補正のみ、四捨五入バージョン
カメラの手ブレ補正入れてみました pic.twitter.com/C5rdSZ1F9l
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月3日
位置・角度補正あり、二捨三入バージョン
手ブレ補正のプログラムをちょっと変えてみた pic.twitter.com/hvvjQ8poQM
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月3日
あまり効果がないような気がする。ここで悩んでても仕方ないので次行きます。
髪の毛をフワフワさせる
SpringBoneが更新されてより使いやすくなりました。また各種調整も入ったためこれからSpringBoneを入れる方は新しいSpringBoneを使いましょう。詳しくは次の記事へ。
ココとココを参考にゆかりさんのもみあげとアホ毛をフワフワさせたい。ついでにスカート?ワンピース?やパーカーのうさみみフードも。
SpringManagerはGithubにある。
SpringBoneはGithubにある。
ってかめんどくさいからunitychanのデータ全部DLしよう。
上手なSpringColliderを付けるコツ
-
体のSpringColliderの大きさと位置を決める
体のSpringColliderは大体3個くらいに分けて、ピラミッド型(もしくは若干のボンキュッボン型)になるように大きさを調整する。体に対して若干小さめにしたほうが自然な感じになる。
ただし頭は少しだけ大きくすることで髪の毛のフワフワ感が出る。
ここでモデルの状態を確認しても二度手間になるため調整してはいけない。後でまとめてやろう。 -
フワフワ対象のSpringBoneのRadiusを決める
SpringBoneを付けるときは体の半分だけ付けて、調整が終わってから全身に貼り付けよう。
髪の毛など細いSpringBoneはRadiusを小さくして、うさみみなどのある程度の大きさを持ったものは初期値のまま、など対象のIKの大きさによってRadiusを調整するととても自然な感じになってハッピーになれる。
あほ毛とうさみみの比較。あほ毛はRadiusを小さくしてうさみみは初期値のままにしてある。 -
不自然な個所の微調整
ここで初めて大きさや位置の調整を行う。
長いもみあげを持つキャラクター(結月ゆかりなど)は、肩のSpringColliderを作ってサイズを小さめに設定するといい感じになる。
ある程度調整を行ったがどうしても不自然な個所がある場合は、元々のIKに回転をかけることも考えること。
ゆかりさんのもみあげは少しだけ前に回転させている。 -
必要ならSpringBoneのStiffness Forceの調整
Stiffness Forceは小さくすると無重力空間にいる状態みたくなる。大きくするとフサフサ感がなくなる。
正直この調整は妥協点を見つけないと無限に時間を持っていかれる。どこかで区切りをつけた方がよい。
問題
- サンプルは動くけどVRコスプレのプロジェクトに適用しても動かない
1つめの原因はココを見て解決。
2つめの原因は謎。なんかSpringManagerとかSpringBoneとか設定メチャクチャ試したけど動かなかった。全部1回削除して再度適用しなおしたら動いた。謎。
できたもの
ゆかりさんのうさみみが揺れるようになったよ!
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月4日
左は適用前、右が適用後 pic.twitter.com/qFiSdoXmHa
特定のコメントを受信したら色々なモデルを出現させたい
コメントをオブジェクトとして出現させる
コレを使ってやります!アンコちゃんからUnityへコメントを引っ張ってきて、コメントを3DモデルとしてUnity内に出現させたい。つまりみゅみゅさんのパクリ。
Unityから直接ニコ生のAPIを叩く方法もある。
文字の3DモデルはFlyingText3Dというアセットを使って生成する。FlyingText3Dについてはココを参考に作業すれば問題ない。
-
フォントファイルがある場所を探す
とりあえずメイリオを使いたいので、Windows標準のフォント格納場所であるC:\Windows\Fonts\
にアクセス。メイリオの名前は "Meiryo UI"。 -
.ttcファイルを分解して.ttfファイルにする
ココとか参考にすればいいんじゃないかな -
適当なGameObjectにFlyingTextをアタッチ
-
同じオブジェクトに以下のようなスクリプトをアタッチ
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" で適当なのを選べばいい。
FlyingText3Dのデモ pic.twitter.com/w7xpplWIbU
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月6日
これで動的にテキストの3Dオブジェクトを生成することができるようになりました。
NicoliveCommentRecieverからコメント受信時にCreateTextを呼び出してあげればOK。
Randomとかで出現位置を変えてあげればバラバラに落ちていい感じになるんじゃないかな。
問題
//文字オブジェクトを作成
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();
}
};
}
- Rigidbody の isKinematic を動的にTrueにできない
上記のプログラムだと useGravity はFalseにできるけど、isKinematic をTrueにはできない。
仕方ないのでFreezeAllとかで代用。
//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にしないと意味ないよね。
//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時の脳みそは信用できませんね。眠くなったら素直に寝ましょう。
- 文字オブジェクトがライティングの影響を受けて見えづらい
後でシェーダーを動的に変更するプログラム追加します。
できたもの
ニコ生のコメントを取得して3Dオブジェクトとして生成するようにしました
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月7日
(全然関係ない方のニコ生のコメント) pic.twitter.com/Zqc8NlqAso
モザイクで遊びたい
Unity AssetStoreまとめさんを見ていたらこんな記事を見つけたのでモザイクで遊んでみる。
記事の通りに行えば何も問題はない。
遠近感が失われるオブジェクトなのでどこまで手を伸ばしたら手に取れるのかわかりづらい。オブジェクトの厚さはそれなりにあった方がいいかも。
できたもの
モザイクで遊んでみた pic.twitter.com/xZLNXJd5bm
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月7日
ピースしたい
キャラクターの指の制御にはMMD4FingerControllerやコレなどがありますが、個人的にわかり辛かった(ソースコード読む気がないだけ)のと大きくカスタマイズしたかったのでスクリプトを自作することにしました。
指の角度を変えるだけなのでそこまで難しいスクリプトにはなってないと思います。
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 は、コチラのスクリプトをちょっと改造(親のオブジェクトもリストの中に含めるように)したものです。
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を指定してください。ゆかりさんだとこんな感じ。
指の可動域
ここで例に挙げているのは右手の指です。左手の指は回転方向にマイナスをかけないとおかしくなります。
人差し指から小指までは 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? |
軽くピースさせてみたらいい感じになった pic.twitter.com/GkQyAjAySm
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月7日
できたもの
指を変えられるようになったよ pic.twitter.com/gQvgoEFAHO
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月7日
UnityNicoliveClient
とりすーぷさん作。
アセット内で使用されているSendWebRequestがUnity2017.2以上じゃないと実装されていないので、Unityのバージョンを再度変更。
Microphoneの問題もあるのでUnity2017.2.0b10をインストール。Fixed in future releaseってなってたけど、どのバージョンで修正してくれるんだろう。
無理にこれを使う必要はないので、Unity側で修正されたら移行する形でもよさそう。
2/12追記:とりすーぷさん側で2017.1以下でも動くように対応していただきました。ありがとうございます。
実装については別記事にて。
ゆかりさんとスプラトゥーン2 その2
2/10にニコ生で配信しました
浮かび上がった問題点
実装・解決したものは打消し線を入れてます。
既存の問題
- キャプチャ映像と音がズレている
- カメラを持つとブレる
- 生放送の管理ができない
- デスクトップの解像度が高すぎて文字が読めない
- VRIKの再セットアップしたい
- 特定のコメントを受信したら色々なモデルを出現させたい
例えば「ゾウ」と打ったらUnity内にゾウが出現するとか - ボタンを置いていろいろしたい
カメラの切り替えとかオブジェクトの出現とか再配置とかゲーム画面をデスクトップ画面と置き換えるとか - HTC VIVE と Nintendo Switch のコントローラを同時に持つことが難しい
腕にマジックテープなどでVIVEのコントローラを固定すれば何とかできるかも? - ユニティちゃん Candy Rock Star ライブステージ!にあるスピーカー実装したい
新たな問題
- ゲーム画面を見ている時にゆかりさんが後ろを向いている
鏡の設置や配信設定を変えることで対応可能? - やっぱりWebCamTexture重い
- コメントを落とす範囲が広すぎて取りに行けない
- テレポート時に姿勢がおかしくなってる
- PCゲーム配信したい
- 配信用のオブジェクト配置にするのが面倒
Gameウィンドウからフォーカスが外れた時にフレームレートが落ちるマルチプレイしたい- 車のモデル置いてレースクイーンごっことかしたい
表情を変化させたい
Gameウィンドウからフォーカスが外れた時にフレームレートが落ちる
以前からGameウィンドウを最前面に表示していないとフレームレートが落ちる問題を解決できずにいました。
そんな時に以下の情報を頂きました。ありがとうございます。
もしOVRLipSyncを使っているのなら、focusedフラグを使った処理をあらかたコメントアウトすることで対応できます
— inew (@Alupaca1363Inew) 2018年2月11日
もし違えば、Profilerビューで、モードをdetailにしてテストすると、どこが重いのか確認できます
試しにOVRLipSync関連のスクリプトを無効化したところ問題が解決したため、focusedフラグを使用している部分を無効化することにしました。
focusedフラグを使用しているのは OVRLipSyncMicInput.cs で、処理内容は フォーカスが外れた時にマイク入力を破棄する 内容でした。
アプリケーションの終了時だけマイクを破棄してくれればいいので、容赦なくコメントアウトします。
マルチプレイしたい
Photon Unity Networkingを使ってマルチプレイしたい。
このシリーズを見れば基本的なことはわかる。
コールバックメソッドの一覧はこちら。
共通部
実装した方がいい機能
- マスターでログインするかゲストでログインするかで処理を変えた方がいい
- マスターからゲストを強制ログアウトさせる機能を実装した方がいい
- Roomを作れるのはマスターだけにした方がいい
基本的な枠組みはコチラから拝借。
このスクリプトを適当な空オブジェクトにアタッチしてあげればいい。
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。
結月ゆかりモデルをプレハブ化していないけどいいの?と思うが、プレハブ化して~のくだりは動的にオブジェクトを生成する場合にのみ必要。最初から存在するオブジェクトはそのままでいい。
ココとか参考になるかも。
ゲスト側
ゲストユーザ向けにビルドするときの設定がややこしい。
ココを参考にゲーム起動時の解像度設定画面を表示するように。SteamVRが文句言ってくるけど無視。
PhotonObjectのLoginスクリプトのIsMasterのチェックを外して、Project SettingのQualityをGood以上に設定。これをしないと光源が一部削除される。
ゲストユーザの接続の前にワンクッション置きたかったため、簡単なログインページみたいなのを作ってみました。
タイトル画面の作り方はココを、InputFieldから情報を抜き出すのはココを参考にしました。
今はplayerNameに設定しているだけですが、ゲストの頭の上に名前とか表示出来たらよさそうですね。
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");
}
}
カメラの移動にはワールド座標系を用いれば難しくない。
ココとココを参考に以下のスクリプトを作成。
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);
}
}
}
ゲストを強制ログアウトさせる機能はココとココを参考に、上記スクリプトに以下を追加
//クライアント終了を通知する関数
public static void SendEnd()
{
photonView.RPC("AppEnd", PhotonTargets.Others);
}
//クライアント終了処理
[PunRPC]
void AppEnd()
{
Application.Quit();
}
あとはマスター側でSendEnd関数を呼べばいい。
問題
-
StartでInstantiateしたら
Failed to Instantiate prefab: CameraCube. Client should be in a room. Current connectionStateDetailed: PeerCreated
って言われた
RoomにJoinする前にInstantiateしようとしているのが問題。Joinした後に生成すれば問題ない。 -
別のユーザのカメラが表示されてしまう
プレハブに登録したオブジェクトのCameraコンポーネントを無効化しておき、ログインしたタイミングでそのユーザが持っているカメラオブジェクトのCameraコンポーネントだけを有効化すれば問題ない。
//ゲストのカメラを生成する関数
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;
}
- ゆかりさんに動的にPotonViewとかPhotonTransformViewとかAddComponentしてもPhotonViewに情報渡すときに
null reference exception
と怒られる
ゆかりさんのモデルにPhoton関連のスクリプトを何もアタッチせず、動的にすべてのIKに対してPhoton関連をアタッチしようと考えて以下のスクリプトを書きました。
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
とエラーを吐かれてしまいました。
いろいろ調べたところ、ここに書いてあることが起きていたっぽい。
詳しく説明すると、変数の生成タイミングの違いによって起こっていたエラー。
FoldoutされているList型の変数が生成されるタイミングは、手動でAddComponentしたときは即座に、動的にAddComponentしたときはインスペクタで該当するGameObjectを見たら生成される。
Runした直後にアクセスしようとしてもインスペクタでGameObjectを見てないから変数が生成されていない。よってエラーが吐かれる。
正直この仕様考えたやつに対してぶん殴りたい。 小一時間問い詰めたい。 もしかしたら初期化すればいいのでは?ココとか参考になる。
対策としては事前に手動でコンポーネントをAddしておけばOK。右矢印とかShiftとか駆使してモデルのIKを全選択しよう。
ただし文字オブジェクトをどうやって同期させるかが非常に悩ましいことになった。
掴んだ時にイベントを発行してチャットでTransformの情報を送ってやる方法を考え中。
-
マスター側でゲストのカメラを移動させても同期しない
ゲストのカメラにVRTK関連を付けて持ち運べるようにしているのだが、移動がゲスト側に同期されない。対策案を検討中。 -
なぜかゲストクライアントのCPU負荷がめちゃくちゃ高い
ココとか参考になるかも。
できたもの
バーチャルアイドルちゃんねるの生主の方々にお手伝いいただきテストプレイをしました。
バーチャルアイドルちゃんねるの生主の方々に手伝っていただいて、ゆかりさんとテストプレイ(意味深)しました。
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月11日
その1 pic.twitter.com/9ziFhU3E22
その2 pic.twitter.com/P1mhEqba83
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月11日
その3 pic.twitter.com/Qv47ZUe2IU
— karukaru@VTuberハッカソン→AMGモータース (@_karukaru_) 2018年2月11日
表情を変化させたい
ココとかAutoBlink.csとかを参考にして以下のスクリプトを書きました。
指を動かすスクリプトと似たようなものです。関数にPunRPCを付けて表情が変化したときにゲスト全員に変更通知が行くようにしています。
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でもスクロール必須になったり、記事の保存が重かったり、編集したい箇所の把握が難しくなってきたりしているので、これ以降の更新は別記事に書きたいと思います。
新しい記事はコチラです。