- 投稿日:2020-08-30T20:19:52+09:00
Unityのシェーダーで頂点カラーを使ったディゾルブ
はじめに
Unreal Engineのビジュアルスクリプティングで試したものを、シェーダーを理解するためにUnityでも再現してみたという話です。
2020年7月のUE4 VFX Art Dive Onlineのスクエア・エニックスの林さんによる講演『ディゾブルマテリアルで表現する立体魔法陣』で、シェーダーに興味を持ちました。まず、講演の途中で出てきた作例そのままをUEで試したもの(講演だけでは分からないところは推測で調整したもの)が以下になります。
Unityのシェーダーにも興味があったのですが、せっかくだからコードでシェーダーを書く方法も勉強したいと思ったので、UEで作ったものをUnityで再現して勉強した記録がこの記事の内容です。なので、技術の解説ではなく、今回勉強したUnityのシェーダーの書き方の覚え書きであることをご了承ください。(技術面は林さんのご講演をご覧ください。)
使用するモデル
林さんの講演通りにHoudiniでモデルを作成しました。今回の処理に必要なのは、ディゾルブに使う頂点カラーとディスプレースメントに使用するNormal Vectorです。
単純なディゾルブ
Shader "Custom/Dissolve" { Properties{ _EmissionColor("Emission Color", Color) = (0.3, 0.7, 1, 1.0) } SubShader{ Tags { "Queue" = "Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard vertex:vert alpha:fade #pragma target 3.0 half4 _EmissionColor; float TimeLoop(float time) { float TimeScale = 0.5; float Adjust = 1.5; float periodic = frac(time * TimeScale) * 2 - 1; //-1から1ののこぎり float tmp = abs( periodic ); //0から1の三角波 //拡大し、拡大量の半分ずらす return tmp * Adjust - (Adjust - 1) * 0.5; // -Adjust/2から1+Adjust/2の三角波 } struct Input { float4 vertColor; float alpha; }; void vert(inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.vertColor = v.color; o.alpha = v.color.x; } void surf(Input IN, inout SurfaceOutputStandard o) { float EdgeWidth = 0.05; o.Alpha = 1 - step( TimeLoop(_Time.y) + EdgeWidth, IN.alpha ); o.Emission = _EmissionColor * step(TimeLoop(_Time.y), IN.alpha); } ENDCG } FallBack "Diffuse" }メモ
- プロパティを使うには、SubShaderでも変数の宣言が必要。floatは高精度浮動小数点だが、halfは中程度浮動小数点、fixedは低精度固定小数点。いろんな記事を参考にしたので、これらが混じっているが使い分けがどの程度必要かは不明。
- タグ。"Queue"は描画順を表す。"RenderType"は何も指定していないが、問題ないのだろうか?
- #pragma surface surf Standard vertex:vert alpha:fade の行について。Standardはライティングモデルオプションを表す。何を指定するかでsurfのoutputの種類が変わる。StandardならSurfaceOutputStandard。vertex:vertとalpha:fadeはオプションパラメータ。それぞれ、vertexシェーダーを使うこととfade-transparencyを使えるようにすることを伝えている。
- vertexシェーダーからsurfaceシェーダーにデータを渡すときの構造体は自分で定義する。今回はInput。
ワールド座標を用いたスクロール
Shader "Custom/Position" { SubShader{ Tags { "Queue" = "Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard alpha:fade #pragma target 3.0 float TimeLoop() { float TimeScale = 0.5; float Adjust = 1.5; float periodic = frac(_Time.y * TimeScale) * 2 - 1; //-1から1ののこぎり float tmp = abs( periodic ); //0から1の三角波 //拡大し、拡大量の半分ずらす return tmp * Adjust - (Adjust - 1) * 0.5; // -Adjust/2から1+Adjust/2の三角波 } struct Input { float3 worldPos; }; void surf(Input IN, inout SurfaceOutputStandard o) { float Bounds = 1; float position = IN.worldPos.x; float SStepWidth = 0.3; float max = (SStepWidth + 1) * TimeLoop(); float min = max - SStepWidth; o.Alpha = smoothstep(min, max, (position/Bounds + 1) * 0.5); } ENDCG } FallBack "Diffuse" }メモ
- ワールド座標を使う作例。InputでworldPosと書くだけで、ワールド座標が入っている?
- のちの処理のためにスムースステップを使っている。
ディゾルブとスクロールを合わせる
Shader "Custom/DissolvePosition" { Properties{ _EmissionColor("Emission Color", Color) = (0.3, 0.7, 1, 1.0) } SubShader{ Tags { "Queue" = "Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard vertex:vert alpha:fade #pragma target 3.0 half4 _EmissionColor; float TimeLoop() { float TimeScale = 0.1; float Adjust = 1.5; float periodic = frac(_Time.y * TimeScale) * 2 - 1; //-1から1ののこぎり float tmp = abs(periodic); //0から1の三角波 //拡大し、拡大量の半分ずらす return tmp * Adjust - (Adjust - 1) * 0.5; // -Adjust/2から1+Adjust/2の三角波 } struct Input { float3 worldPos; float4 vertColor; float alpha; }; void vert(inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.vertColor = v.color; o.alpha = v.color.x; } void surf(Input IN, inout SurfaceOutputStandard o) { float Bounds = 3; float position = IN.worldPos.x; float SStepWidth = 0.3; float max = (SStepWidth + 1) * TimeLoop(); float min = max - SStepWidth; float Adjust = 1.5; float EdgeWidth = 0.05; float inLoop = 1 - (Adjust * (smoothstep(min, max, (position / Bounds + 1) * 0.5 ) - (Adjust - 1) * 0.5)); o.Alpha = 1 - step(inLoop, IN.alpha); o.Emission = _EmissionColor * step(inLoop - EdgeWidth, IN.alpha); } ENDCG } FallBack "Diffuse" }メモ
- 講演のノードでうまくいくのかよく分からなかったので、自分なりに変更しています。
さらにバンプ関数を作って、一方向のスクロールも作ります。
Shader "Custom/DissolvePosition2" { Properties{ _BaseColor("Base Color", Color) = (0.1, 0.12, 0.15, 1.0) _EmissionColor("Emission Color", Color) = (0.3, 0.7, 1, 1.0) } SubShader{ Tags { "Queue" = "Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard vertex:vert alpha:fade #pragma target 3.0 half4 _EmissionColor; half4 _BaseColor; float TimeLoop() { float TimeScale = 0.15; float Adjust = 1.5; float periodic = frac(_Time.y * TimeScale); //0から1ののこぎり //拡大し、拡大量の半分ずらす return periodic * Adjust - (Adjust - 1) * 0.5; // -Adjust/2から1+Adjust/2の三角波 } struct Input { float3 worldPos; float4 vertColor; float alpha; }; void vert(inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.vertColor = v.color; o.alpha = v.color.x; } void surf(Input IN, inout SurfaceOutputStandard o) { float Bounds = 3; float position = IN.worldPos.x; //バンプ関数をつくる float SStepWidth1 = 0.3; float SStepWidth2 = 1; float SStepWidth3 = 0.3; float tmp = (SStepWidth1 + SStepWidth2 + SStepWidth3 + 1) * TimeLoop(); float min1 = tmp - (SStepWidth1 + SStepWidth2 + SStepWidth3); float max1 = tmp - (SStepWidth2 + SStepWidth3); float min2 = tmp; float max2 = tmp - SStepWidth3; float Adjust = 1.5; float bump = saturate((smoothstep(min1, max1, (position / Bounds + 1) * 0.5))) * saturate((smoothstep(min2, max2, (position / Bounds + 1) * 0.5))); float inLoop = 1 - (Adjust * bump - (Adjust - 1) * 0.5); float EdgeWidth = 0.05; o.Albedo = _BaseColor; ![disolveposition2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/313474/2bdb30d8-bd98-bef2-b3db-d3cc3942c3e6.gif) o.Alpha = 1 - step(inLoop, IN.alpha); o.Emission = _EmissionColor * step(inLoop - EdgeWidth, IN.alpha); } ENDCG } FallBack "Diffuse" }
- TimeLoopの関数を三角波からのこぎり波に変更しています。
ディスプレースメント
講演の作例と同様にディスプレースメントもつけました。
Shader "Custom/Displace" { Properties{ _BaseColor("Base Color", Color) = (0.1, 0.12, 0.15, 1.0) _EmissionColor("Emission Color", Color) = (0.3, 0.7, 1, 1.0) } SubShader{ Tags { "Queue" = "Transparent" } LOD 200 Cull Off CGPROGRAM #pragma surface surf Standard vertex:vert alpha:fade #pragma target 3.0 half4 _EmissionColor; half4 _BaseColor; float TimeLoop() { float TimeScale = 0.15; float Adjust = 1.5; float periodic = frac(_Time.y * TimeScale); //0から1ののこぎり //拡大し、拡大量の半分ずらす return periodic * Adjust - (Adjust - 1) * 0.5; // -Adjust/2から1+Adjust/2の三角波 } float BumpLoop(float position) { float Bounds = 3; //バンプ関数をつくる float SStepWidth1 = 0.3; float SStepWidth2 = 1; float SStepWidth3 = 0.3; float tmp = (SStepWidth1 + SStepWidth2 + SStepWidth3 + 1) * TimeLoop(); float min1 = tmp - (SStepWidth1 + SStepWidth2 + SStepWidth3); float max1 = tmp - (SStepWidth2 + SStepWidth3); float min2 = tmp; float max2 = tmp - SStepWidth3; float Adjust = 1.5; float bump = saturate((smoothstep(min1, max1, (position / Bounds + 1) * 0.5))) * saturate((smoothstep(min2, max2, (position / Bounds + 1) * 0.5))); return 1 - (Adjust * bump - (Adjust - 1) * 0.5); } struct Input { float3 worldPos; float4 vertColor; float alpha; }; void vert(inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.vertColor = v.color; o.alpha = v.color.x; v.vertex.xyz += 0.05 * v.normal * (1 - saturate(BumpLoop(v.vertex.x) )); } void surf(Input IN, inout SurfaceOutputStandard o) { ![displace.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/313474/b6224be8-514b-3286-2c9c-bbedf314b54f.gif) float EdgeWidth = 0.05; o.Albedo = _BaseColor; o.Alpha = 1 - step(BumpLoop(IN.worldPos.x), IN.alpha); o.Emission = _EmissionColor * step(BumpLoop(IN.worldPos.x) - EdgeWidth, IN.alpha); } ENDCG } FallBack "Diffuse" }メモ
- 両面表示するためにCull Offを追加した。
- vertでもバンプ関数が必要になるため、surfの外で関数を定義しなおした。
おまけ
数式の処理を書く場合、ビジュアルスクリプティングよりコードの方が圧倒的にいいと思っていました。ノードからコードに書き換える作業をすることで、けっこう問題は複雑だと感じました。
ノードだとどの値が他の計算で使われているか一目でわかります。コードの場合、再利用したいものは変数として定義する必要があります。
例えば、ノードで出力がたくさん出ていれば、それを変数で定義すればコードが簡単に書けると判断できます。しかし、何回も使われるノードはけっこうあるので、2回以上使われているからといってすべて変数にしていては大変なことになります。
数式で書く利点は、ひとまとめの式として意味を把握しやすいことなので、逆に言えば、そのようにうまく途中式や関数を書く必要があります。
ビジュアルスクリプトは、簡単な数式でも書くのが面倒ですが、値が別の場所でどのように使われているかというのが一目で分かります。ビジュアルスクリプティングはコードベースの書き方と比べて、多くの人が思っている以上に思想の違うものだと思います。そして、別物として正しく捉えることができれば、読みやすく使いやすいプログラムが書けるようになるのだと思います。そのための新しい捉え方を考えて、まとめたいと思っています。
参考サイト
ディゾブルマテリアルで表現する立体魔法陣
【Unityシェーダ入門】透明なシェーダを作る
【Unityシェーダ入門】シェーダで旗や水面をなびかせる
Shader(HLSL), 手続き的にテクスチャ生成など行うとき使用頻度の高い関数
- 投稿日:2020-08-30T16:45:38+09:00
Fungusをなんとなくそれっぽく使う
準備するよ!
①AssetStoreから「Fungus」を探してね!
どこかわかんにゃい
→一番上の「Windows」タブの中にあるよ〜!
このきのこのやつね
②何も考えずに全てをインポート
まあまあ時間かかるから茶でもしばいて
文字読むのめんどい人はこっち見て。めっちゃわかりやすいから。
https://youtu.be/evENw4lCyE4使ってみるよ!
・勝手にfungusとかのフォルダ追加されてるからとりま消さないようにする
・「Fungus」→「Resources」→「Prefabs」のフォルダにいく
・「SayDialog」をヒエラルキーにペコっと貼り付けて!(でも消さないでおいてね)
・貼り付けたやつをUnpack Prefabして、自分の作ってるPrefabフォルダにいれる
・「SayDialog」のパネルだのテキストだのイメージだの自分仕様にする・上のバーって一覧あるタブの「Tools」→「Fungus」→「Create」→「Flowchart」
これ出したらSceneViewみたいにずっと表示するように置いておくと便利?・FlowchartのNewBlock押すとこんなの出てくる
・Block Nameは自分がわかる名前でおk
・Execute On EventでFungus(会話ウィンドウ)を呼びたいとこを設定Execute On Event ってどれ使えば良いの?
・Game Started
ゲーム開始時にすぐに呼びたい時に使う・Scene/Message Received
自分でタイミング決めたい人用。スクリプトとかから呼ぶやつ・UI/Button Clicked
ボタンを押して呼びたい時に使う他にも色々種類あるからもっと調べれば良い感じに綺麗にできるんだろうけど
まあ大体この3つでどうにかなると思ってる。
やだ!やめて!怒らないで!FlowchartのNew Blockがあるところで右クリック→Add Blockで新規作成できる
呼びたいタイミングが違うやつとか、ウィンドウごとにじゃんじゃか作ろうね〜実際に会話を作ってみる
さっきのExecute On Eventがあった場所(Blockを押すと出てくるヒエラルキー)
の下の方に「↑」とか「↓」とか「+」とかあるっしょ?「+」押してみ。
げろぉ……なんかめっちゃある。けどここで会話を作ります。Narrative/Say
・普通の会話。タ特に何も考えずにセリフを入れるだけのお仕事。基本はこれ使う。
ちなみにこれ系は全てコピペできるから同じやつをわざわざ探さなくても良いよ。便利!
・Story Textに発言させたいテキストを入れる
・Show Alwaysにチェック入れてると毎回呼ばれる。外すと1回だけしか表示されない
・Wait For Clickのチェックを外すと自動で次にいく。多分。確認してないけどきっとそう…
あとはそんないじらんでも平気かな。Variableってところで条件を入力するんだけど、さっきのFlowchartの下らへんに
「Variables」って白いのが隠れてると思うからクリックして表示。
「+」を押すとBooleanとかIntegerとか色々あるので作りたい条件を設定。
・チェックを入れたらTrue、なきゃFalse。簡単だね!
ここで条件を作っておけばさっきのifブロックで選択できるようになるよ!
ifとかの条件定義→Sayコマンドなど→Endコマンドで挟むように置いてね。
置いたコマンドの左側にある≡で上下の順番を変えられるよ!Endは必須!Variable/Set Variable
・会話の中で条件を変更するやつ
変更したいタイミングで入れて、=でTrue、!=でFalseにする。
この辺は変更してみたら表示されるから目で見て設定できると思う〜
ここまでで他の会話でboolでTrueにして条件で会話内容を変えたり、
話しかける度に内容が変わるとか出来るようになりました!
これだけでもゲームが作れるね選択したりとかObject動かしたりとかしたい
・選択肢
①選択肢の数だけFlowchartでAdd Blockする
選択した後に実行したいことを各ブロックに設定する。
セリフを出す〜とか、BoolをTrueにする〜とか、後でやるけど画面遷移する〜とか②選択肢の数だけNarrative/Menuを追加
さっきの画像でいうNew Blockのところ。
選択肢を出したいBlockに選択肢分のMenuを追加!
・Text:選択肢に表示するテキスト。「はい」とか、「そんなことはできない!」とか
・Target Block:選んだ時に移動する先のBlockを設定
これをすることでFlowchart上で分岐が作られます〜
おわかりいただけただろうか…
もうこれでFungusマスターを名乗っても良いレベルです。
・その他Scriptを利用してFungusを制御していく系とか
Messageで制御
①Execute On EventをMessage Receivedに変更
②その下のMessageって書いてあるところに受け取るMessageを設定(何でもいいよ)これだけで、他の会話ウィンドウとかからSend Messageをしたら呼び出すという動きができる
FungusからScriptを制御
Scripting/Call Method
・使いたいScriptがついてるObjectをTarget Objectにペコっと貼り付ける
・使いたいScriptの名前を書く:()は付けなくていいよ
・Delayで何秒後に実行されるか決めるこれでFungusからScriptを呼ぶことに成功!やったね!
ScriptからFungusを制御
using Fungus;
を必ず付けてね、必ずだよ!!
Flowchart.BroadcastFungusMessage(fuga);
Fungusを呼びたい箇所でこのコードを書く。
fugaの部分にMessageで制御の時にも使ったMessage名を書く。
これでScriptからMessageを送信し、Fungusが呼び出せる!やったねじょにちゃん、Fungusが使えるよ
- 投稿日:2020-08-30T16:28:12+09:00
Unity 2020.1 で ReorderableList が作りやすくなったらしい……?
みんな大好き、ReorderableList
カッコいいエディタ拡張の代名詞たるReorderableList、好きですよね。しかし実際に使おうとすると、ReorderableListはUnityEditorInternalに入っている関係上、マニュアルはおろか、リファレンスすらありません。幸いすでにたくさんの方がその使い方を記事にされており、私もそれらを参考に実装していました。それでもなおその使い方はかなり煩雑で、だいぶめんどくさかったのです。
しかし先日Unity 2020.1のリファレンスを眺めていたところ、以下のような項目が。
Unity - Scripting API: UIElements.ListView.reorderable
なんとUI ElementsのほうのListViewにreorderableというプロパティが実装されていました。プロパティいっこ設定するだけでReorderableにできるというのです。これは試さなくては!意気揚々と実装してみた結果が以下になります。
えっなんか違う……
reorderableってそういう……?そんなにカッコよくはないけどまあ便利そうなので
以下、今回のコードです。
SampleList.csusing System; using UnityEngine; public class SampleList : MonoBehaviour { public SampleListItem[] items; } [Serializable] public class SampleListItem { public string key; public string value; }SampleListEditor.csusing UnityEditor; using UnityEditor.UIElements; using UnityEngine.UIElements; [CustomEditor(typeof(SampleList))] public class SampleListEditor : Editor { public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); var list = new ListView(); list.bindingPath = "items"; list.reorderable = true; list.itemHeight = 70; list.style.flexGrow = 1; root.Add(list); root.Bind(serializedObject); root.style.height = 200; return root; } }まあ実装は楽だし入れ替えは便利だし……
- 投稿日:2020-08-30T15:51:55+09:00
Google Cloud Text-to-Speech .NET用クイックスタートをUnityで実行する方法
やりたいこと
Google Cloud Text-to-SpeechにはUnity用のSDKが用意されていません。
そこで、.NET用のクイックスタートをUnityで実行します。
ソースコードは以下にアップロードしてあります。
https://github.com/AzetaTakuya/GoogleCloudText-to-SpeechForUnity結果
分かりづらいですが、sample.mp3を保存することができました。
sample.mp3を再生するとHello, World!と再生されます。
実装
環境
- Windows10 Home
- Unity 2019.4.8f1
- Visual Studio 2017 (Community)
Google.Cloud.TextToSpeech.V1 -Version 2.0.0- Google.Cloud.TextToSpeech.V1 -Version 1.0.0
※2020/08/30現在 ver2.0.0を使用すると2度目の実行からフリーズしてしまったのでver1.0.0を使用します。原因/対策が分かる方いましたら教えてください。手順
- 認証ファイルの作成
- .NET用ライブラリ(Nuget Package)をインストール
- ライブラリをUnityにインポート
- QuickStartをUnity用に修正
- 実行
※手順は基本的に公式ドキュメント:クイックスタートに従います。①認証ファイルの作成
公式ドキュメント:クイックスタートの①~④までを完了すると、JSONファイルが生成されます。
今回、環境変数の設定はスクリプトから行うので省略して構いません。②.NET用ライブラリ(Nuget Package)をインストール
NugetPackageをUnityにインストールする方法として、NuGetForUnityが有名ですが宗教上の理由で使用しません。
VisualStudioのパッケージマネージャーコンソールからインストールします。VisualStudioプロジェクト作成
VisualStudioを開いて、【ファイル -> 新規作成 -> プロジェクト】からコンソールアプリ(.NET Framework)を作成します。
今回は、プロジェクト名は[TextToSpeechV1]とし、.NET Framework 4.7.1を使用しました。
プロジェクトの作成ができたら、【ツール -> Nuget パッケージマネージャー -> パッケージマネージャーコンソール】から、パッケージマネージャーコンソールを開きます。
パッケージマネージャーコンソールが開いたら以下を実行します。
PM> Install-Package Google.Cloud.TextToSpeech.V1 -Version 1.0.0実行が終了したら、【TextToSpeechV1(※作成したプロジェクト)/Packages】を確認します。
③ライブラリをUnityにインポート
先ほどのフォルダの中にはDLLが入っているので、Unityにインポートできる様に修正します。
作業内容としては、
① Grpc.Core.1.22.0以外のフォルダ内にある【lib/net45/】内のファイルを全てpacakes直下に移動し、Grpc.Core.1.22.0以外のフォルダを全て削除
② 【Grpc.Core.1.22.0/lib/netstandard2.0】と【Grpc.Core.1.22.0/lib/netstandard1.5】を削除
③ 【Grpc.Core.1.22.0/runtimes/win/native】内のgrpc_csharp_ext.x64.dllかgrpc_csharp_ext.x84.dllのどちらかをgrpc_csharp_ext.dllに名前を変更以上が完了したら、Unityプロジェクトを作成します。
Unityプロジェクトを作成したら、先程修正したpakegesフォルダの名前をPluginsに変更し、Unityにインポートします。
これでUnityにライブラリのインポートが完了しました。④QuickStartをUnity用に修正
.NET用のクイックスタートにあるスクリプトをUnity用に修正・環境変数の追加をしたものが以下となります。
using System.IO; using UnityEngine; using System; using Google.Cloud.TextToSpeech.V1; public class QuickStart : MonoBehaviour { public string credentialsPath; public string saveFile; void Start() { #region Environment Variable if (!File.Exists(credentialsPath)) { Debug.LogError("failure" + credentialsPath); return; } else { Debug.Log("success: " + credentialsPath); } Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", credentialsPath); #endregion #region QuickStart // Instantiate a client TextToSpeechClient client = TextToSpeechClient.Create(); // Set the text input to be synthesized. SynthesisInput input = new SynthesisInput { Text = "Hello, World!" }; // Build the voice request, select the language code ("en-US"), // and the SSML voice gender ("neutral"). VoiceSelectionParams voice = new VoiceSelectionParams { LanguageCode = "en-US", SsmlGender = SsmlVoiceGender.Neutral }; // Select the type of audio file you want returned. AudioConfig config = new AudioConfig { AudioEncoding = AudioEncoding.Mp3 }; // Perform the Text-to-Speech request, passing the text input // with the selected voice parameters and audio file type var response = client.SynthesizeSpeech(new SynthesizeSpeechRequest { Input = input, Voice = voice, AudioConfig = config }); // Write the binary AudioContent of the response to an MP3 file. using (Stream output = File.Create(saveFile)) { response.AudioContent.WriteTo(output); Debug.Log($"Audio content written to file " + saveFile); } #endregion } }⑤実行
credentialsPathに認証ファイルのパスを、saveFileに保存ファイルパス(.mp3)を入れ実行すると音声ファイルが保存されます。
まとめ
上手くできないという声が多い気がしたので書いてみました。
GoogleCloutPlatform自体の使い方については結構雑な感じなので要望があれば書こうと思います。
なんでGoogle.Cloud.TextToSpeech.V1 -Version 2.0.0では安定動作しないんだ...?
気が向いたらWindows以外も対応します。参考
なし
- 投稿日:2020-08-30T10:45:50+09:00
【unityroom】再アップロード時のエラー解決法
unityroomゲームジャムに参加している時、公開しているゲームでバグが発生すると
WebGL関連のファイルを再度アップロードする必要が出てきますよね。そのときによく遭遇したエラーがあったのでその対応について簡単に備忘録を書いておきたいと思います。