20200113のUnityに関する記事は12件です。

Unity初心者がUnityでLive2Dモデルを動かすため環境構築をした

UnityでLive2Dを動かしたい

UnityでLive2Dを動かすための環境構築やサンプルモデルのインポートについてまとめました。
【初心者向け】UnityとLive2Dで拡張しやすいVTuber配信システムを作る方法がとても詳しいので、基本この記事を見てもらってたら作れます。
自分はUnity初心者で躓いた手順があったので、それを補う形で記事を書きました。

UnityでLive2Dモデルを動かす環境構築

  1. 2Dで新しいUnityプロジェクトを作成
  2. Live2D SDKのダウンロード
    UnityでLive2Dを使うにはLive2d Cubism SDKをUnityにインポートする必要がある。
    SDKは以下からダウンロードできる。
    https://www.live2d.com/en/download/cubism-sdk/
    因みに、(2019/01/13現在)Unity Editor 2019.2.11f1ではiOS、iPad OS、WebGLビルドエラーが起こるとのことなので、それよりバージョンの低いUnityでインポートしましょう。
    Unity 2019.2.15f1だとプラットフォームWindows64bitで以下のエラーになって原因分からず困ってました。 image.png
    Unity 2018に変えたらエラー起こりませんでした。
  3. UnityにLive2D SDKをインポート メニュー Assets > Import Packege > Custm Packege… を選択し、ダウンロードしたファイル(.unitypackage)を選択。
    image.png
    以下のダイアログが出るので、そのままImportボタンを押下。
    image.png
    完了したらUnityを再起動(Consoleのコンパイルエラーが解消される)。

以下のAnimationのScenesがPlayボタンで動けば無事Live2Dを動かす環境が整ってます。
image.png

サンプルモデルのインポート

  1. モデルのダウンロード
    今回は以下のサンプルモデルから桃瀬ひよりのFree版を使います。
    https://docs.live2d.com/cubism-editor-manual/sample-model/
    (桃瀬ひよりはCubism3.0と新しいバージョンで作られているためこのモデルを選択してます)
    ダウンロードしたzipを解凍すると以下のファイルが格納されているのですが、 image.png
    UnityにLive2Dのモデルをインポートするときmoc3ファイルが必要です。
    Live2D Cubism Editorをダウンロードしてmocファイルを生成します。
    詳しい手順は、Live2D Cubism Editor マニュアル moc3ファイルの書き出しを参考に。
  2. モデルのインポート
    mocファイルを生成したら任意のフォルダに以下のファイルを一か所にまとめて image.png
    フォルダをProjectタブのAssetsにドラッグアンドドロップすると、
    image.png
    プレハブファイル(青いブロックのアイコン)が生成されます。
    プレハブについて
    プレハブは再利用可能なゲームオブジェクトで、オブジェクト指向言語でいうクラスと例える記事もあります(今日からはじめるUnity - Qiita)。
    プレハブをSceneに追加するとプレハブのインスタンスが生成されます。以下の手順3がそうですね。
    プレハブのインスタンスは、プレハブを変更すると、全てのプレハブのインスタンスにも反映されます。
    詳しくは、Unity マニュアル プレハブを参考に。
  3. モデルをSceneに置いてみる
    Sceneを新規に作成してダブルクリックで選択し、HierarchyタブのScene(画像ではMain)に先ほど生成されたプレハブファイルをドラッグアンドドロップします。
    以下のようにモデルが表示されます。
    image.png
    ちょっと遠いので、Main CameraのSizeを小さくします。1をいれればこれくらい大きく表示されます。
    image.png

Playボタンを押下し、InspectorタブのCubism PArameters Inspector(Script)のパラメータを動かした時、モデルが動いたら無事インポート完了です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityゲーム開発3Dアプリの解像度とfps

以下現職の方に聞いたメモ

  • 端末の解像度に応じてカメラの位置を替えたりすることもできる。
  • また、3Dのゲームアプリで高品質、低品質といった設定を選択できるものがあるが、これは「レンダーテクスチャ」を使えば実現できる。
    • レンダーテクスチャを使えば、その他例えばゲームシーン中に監視カメラのオブジェクトを用意してその監視カメラに描画する、といったこともできる。
  • 低品質にするには、例えばレンダーテクスチャの解像度を1/2にしてこれをキャンバスに描画するみたいなイメージ。
  • レンダーテクスチャの解像度(pixel数)が下がれば負荷も下がる。
  • プログラミングなしで実現可能。
  • fpsはバッテリーに影響する。60fpsで3Dだとあっという間に減っていく。
  • fpsが出ないのはスクリプト側よりもレンダリングの方の影響が大きいケースが多い
  • ゲームビュー右上のStatsというところのBatchesという部分が重要。80くらいに抑えられるのがよい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityゲーム開発での実機テスト

以下現職の方に聞いたメモ

  • 推奨端末はシェアで決めたりする。
  • iPhoneはメモリがつらいがCPUは高性能なので動作がカクつくことがあまりない
  • Androidは機種によりまちまち。XPeria、Galaxyといった人気機種では熱が上がってしまってCPU性能が制限されることがある。
  • クラッシュログを送信してくれるミドルウェアをアプリに仕込んでおくと、リリース後の不具合対応に役に立つ。
  • エラーを例外処理で握りつぶしまくると、重要なログがとれなくなってしまうことがあるので要注意。
  • 個人開発をする上では、コスト上実機検証をしっかりとすることは難しいかと思う。
  • メモリやCPU負荷周りの動作確認をするのであれば自身が持っている端末で十分では。
  • ちゃんと画面に映っているか?という解像度周りのチェックはUnityエディタ上ですればよい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】一定時間ごとに指定した関数を実行する方法

1. Google検索にて一定時間で実行する方法を検索すると

検索上位の情報では以下の様なヒントが出てくると思います。
- Time.deltaTime
- Time.time

2. Time.deltaTimeなどを使った時のデメリット

  • 時間管理をミスる可能性が高い
  • バグの原因になる可能性がある

この2種類の関数を用いた時の特徴としてはIF文を書く必要がありますが、沢山の処理があった場合は可読性を落とす事なります。

3. 何を使えばいいの?

InvokeRepeating()を使用すればキレイに解決します。

簡単な説明は以下の通りです

InvokeRepeating("使用したいメソッド名", 開始時間, 何秒後に再実行);



サンプルコードは以下の通りです。(スマホの位置情報を一定時間毎に更新したい)

sample.cs
/// <summary>  起動時間(秒) </summary>
private const float START_SECONDS = 0.0f;
/// <summary>  更新間隔時間(秒) </summary>
private const float INTERVA_SECONDS = 1.0f;

// Start is called before the first frame update
void Start()
{
  InvokeRepeating("UpdateMyPhoneLocation", START_SECONDS, INTERVA_SECONDS);
}

private void UpdateMyPhoneLocation(){
  //GPSデータを取得
}

4. InvokeRepeatingのメリットは?

読んだ人は思うでしょう。。。
Time.deltaTime等と違って可読性が高いが最大のメリットだと思います。


もっといい方法(アドバイス)や感想があったらコメントしてください以上。

[参考]https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.InvokeRepeating.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OVR Scene Quick Previewで変更したシーンのOculus Go/Quest実機確認を爆速にする

はじめに

Oculus Go/Questのアプリはビルドが長く実機確認に時間がかかります。ところが以下のツイートでOVR Scene Quick Previewを使うと爆速で開発できるらしいので使い方を調べました。

使い方

OVR Quick Scene Previewに使い方があるので参考にしました。

動作環境

  • 必須のもの
  • 推奨するもの
    • USB 3.0ケーブル。転送速度が50%向上するようです。比較対象が書いてませんが恐らくUSB2.0と比べた場合と思われます。

前準備

まずは前準備として以下の手順でアプリとシーンをビルドしてGo/Questに転送します。ここはまだ時間がかかります。

  1. Unityのビルド設定を開いてデバイスにGo/Questが見えている事を確認します。
  2. メニューのOculus > OVR Build > OVR Scene Quick Previewを選択して同名のダイアログを開きます。 image.png
  3. 2つ目のボタンのBuild and Deploy Appをクリックして転送用アプリをビルドしてGo/Questにインストールします。 image.png
    1. Go/QuestにアプリがインストールされるとStatusに緑でAPK installed. Ready to build and deploy scenes.と表示されます。
    2. Go/Questでは正面に白いパネルが表示されていますがまだシーンは表示されません。
  4. 1つ目のボタンのOpen Build Settingsをクリックしてプレビューしたいシーンをビルド設定に追加します。シーンは少ない程ビルドが速くなるので1つが一番良いのですが複数追加する事もできます。 image.png
    1. シーンを追加するとScenesの下にシーン名が表示されてボタン名がBuild and Deploy Scene(s)に変わります。
  5. ボタン名が変わったBuild and Deploy Scene(s)をクリックしてシーンをビルドしてGo/Questに転送します。
    1. Go/Questで正面の白いパネルに転送されたシーン情報が表示されて3秒後に消えます。

爆速プレビュー

  1. 前準備で追加したシーンを変更します。
  2. OVR Scene Quick Previewダイアログを開きます。
  3. 1つ目のボタンのBuild and Deploy Scene(s)をクリックしてシーンをGo/Questに転送します。
  4. 変更した内容によりますが数秒で実機に反映されます。

仕組みとオプション

仕組み

プロジェクトのシーンを複数のAssetBundleに分割してそれをロードするアプリを経由する事で素早く実機確認できる仕組みとなっています。2つ目のボタンでビルドしたアプリがロード用アプリで1つ目のボタンでシーンをAssetBundleにビルドしています。
またロード用アプリの白いパネルがあるシーンはAssets/Oculus/VR/Editor/Scenes/OVRTransitionScene.unityに格納されています。

オプション

  • Force Restartチェックボックス
    • チェックするとAssetBundle化したシーンをロードする時にアプリを再起動します
  • Bundle Management
    • Delete Device Bundlesボタン
      • Go/Questに転送したAssetBundleを削除します
    • Delete Local Bundlesボタン
      • PCでビルドしたAssetBundleを削除します
  • Use optional APK package nameチェックボックス
    • チェックするとロード用アプリのパッケージ名の最後に.transitionを追加します
  • Launch Appボタン
    • Go/Questでロード用アプリを起動します
  • Open Build Settingsボタン
    • Unityのビルド設定を開きます
  • Uninstall APKボタン
    • Go/Questからロード用アプリをアンインストールします
  • Clear Logボタン
    • OVR Scene Quick Preview下部のログメッセージをクリアします

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity WebGLで使われているシェーダを抜き出してARBアセンブリを眺める

最近GPUを作りたいんだけど、良いアプローチを思いついていない。懐しのARB Assemblyくらいなら行けるんじゃないかということでUnityアプリをWebGLビルドし、そのシェーダを抜き出してnVidiaのCg Toolkit(懐しいな)で処理してアセンブリにしてみた。

結果、WebGLの世界と言えども 流石にDirectX 8時代のテクノロジでは少々厳しい ということが判った。

ARB Assembly

いわゆるGPUシェーダはDirectX8で導入され、DirectX9でHLSL(C言語風のシェーダ記述言語)が導入され 今に至る (= WebGL1ではDirectX9 世代のGPU機能しか使えない)が、ARB AssemblyはちょうどDX8とDX9の中間あたりの機能性を提供する。

命令セットは比較的コンパクトで、ピクセルシェーダで 33命令、頂点シェーダで27命令が定義されている。いくつかのポイントは:

  • 今でも使える 。実はこのOpenGL拡張は今でも大抵のDesktop GL環境でサポートされている。 ...MojoShaderみたいにGLSLに変換してから実行する処理系になっているかもしれないけど。
  • 分岐が存在しない 。分岐命令がなく、プログラムの実行速度が一定になる。これはWebGLでも前方分岐しか許されていないのでWebGLを実装する上では大きな障害にはならない。
  • 命令セットは頂点シェーダとピクセルシェーダでほぼ同じ。

DX10やOpenGL 2.0以降はアセンブリのサポートは無くなったが、VulkanではSPIR-Vが導入され命令を直接書くことは一応可能になった。

spector.js 拡張でWebGL描画をトレースする

というわけで、ARB Assemblyが現代に蘇えったとして、どのくらい戦えるのかを知りたい。これにはWebGLなゲームを動かしてARB Assemblyにコンパイルしてみるのが良いかなと思った。WebGLはちょうどDirectX9での実行を想定してOpenGL ESをサブセットしているので、WebGLに対応したものであれば打率が高まるはず。

この手の処理にはspector.js( https://spector.babylonjs.com/ )が便利。拡張をインストールするとブラウザにボタンが追加され、クリックすることで キャプチャ取得モードでページをリロード → さらにクリックでキャプチャメニュー表示 となる。

今回はある程度複雑なシェーダを使おうということで、Unity-chan Toon Shader2 ( https://github.com/unity3d-jp/UnityChanToonShaderVer2_Project ) のサンプルをWebGLビルドして使った。ビルド設定はForward renderingを強制するためにWebGL1ビルドに設定している。(Graphics APIsで、WebGL 1.0だけを残す。)

SnapCrab_NoName_2020-1-13_18-37-30_No-00.png

通常のWebサイト同様、Chromeに表示させて、キャプチャボタンを押せばキャプチャできる。

SnapCrab_NoName_2020-1-13_18-13-6_No-00.png

... この時点で何か描画おかしくないか。。? アウトライン抽出がおかしい気がする。

キャプチャしたデータは、json形式でエクスポートできる。

SnapCrab_NoName_2020-1-13_18-15-23_No-00.png

シェーダの切り出し

// Extract WebGL1 shaders from spector.js trace output "capture.json"

const path = require("path");
const fs = require("fs");

const capture = JSON.parse(fs.readFileSync(path.resolve(__dirname, "capture.json")).toString("utf8"));

capture.commands.forEach(cmd => {
    if(cmd.DrawCall){
        cmd.DrawCall.shaders.forEach(s => {
            const ext = (s.SHADER_TYPE == "VERTEX_SHADER") ? ".vert" : ".frag";
            fs.writeFileSync(s.shader.__SPECTOR_Object_TAG.id.toString() + ext,
                             s.source);
        });
    }
});

適当にこういう感じで切り出す。切り出したものは拡張子 .frag (フラグメントシェーダ、いわゆるピクセルシェーダ) と .vert (バーテックスシェーダ) のどちらかになる。

このシーンだけで42個ものシェーダが出力された。実際には内容が同じものもあるだろうけど、大きいものでは28KiBとわりとヤバいサイズになっていた。

SnapCrab_NoName_2020-1-13_18-19-25_No-00.png

Cg Toolkit でGLSLをコンパイルする

nVidiaのCg Toolkit( https://developer.nvidia.com/cg-toolkit )は一般に普及したC言語風シェーダコンパイラとしては始祖にあたるもので、2012年でHLSLやGLSLにその役割を譲ってメンテナンスを終了した。

これ自体はnVidiaの公式シェーダ言語フロントエンドという側面もあり、 実はGLSLのコンパイラも兼ねている

で、コンパイラはDirectX 8〜9世代のベンダ間共通GPUアセンブリである ARB_vertex_program とか ARB_fragment_program 向けのアセンブリコードも出力できる。

$ cgc -ogles -profile arbvp1 28.vert # 頂点シェーダの場合arbvp1を使う
$ cgc -ogles -profile arbfp1 23.frag # ピクセルシェーダの場合

コンパイルしたシェーダはこういう感じのアセンブリ命令列になり、

SLT R0.x, R0, -c[3].w;
ABS R0.x, -R0;
CMP R0.x, -R0, c[3].y, c[3];
MOV result.color, c[3].x;
KIL -R0.x;

GLSLコンパイラを実装するよりはマシかなという気持ちになれる。

エラーを眺める

が、流石にDirectX9世代のWebGL1とDirectX8〜9世代のARB Assemblyでは機能的に格差があり正常にコンパイルできなかった。

LODと微分

大きいピクセルシェーダでは大体次のように失敗する:

23.frag
23.frag(494) : error C3004: function "vec4 tex2D(sampler2D, vec2, vec2, vec2);" not supported in this profile
23.frag(494) : error C3004: function "vec2 ddx(vec2);" not supported in this profile
23.frag(494) : error C3004: function "vec2 ddy(vec2);" not supported in this profile
597 lines, 3 errors.

これは多分 EXT_shader_texture_lod で、常識的なGL環境ではサポートされている... ってWebGL Statsだと全然サポートされてないな。。( https://webglstats.com/webgl/extension/EXT_shader_texture_lod )

エラーになっている行は:

#version 100
#ifdef GL_EXT_shader_texture_lod
#extension GL_EXT_shader_texture_lod : enable
#endif
#if !defined(GL_EXT_shader_texture_lod)
#define texture2DLodEXT texture2D
#endif

    u_xlat10_4.xyz = texture2DLodEXT(_MatCap_Sampler, u_xlat4.xy, _BlurLevelMatcap).xyz;

これは マットキャップ の貼り付けで使われるパラメタで、

Mip Map機能を利用して、MatCap_Samplerをぼかします。Mip Mapを有効にするためには、テクスチャインポートセッティングで、Advanced > Generate Mip MapsON にしてください。デフォルトは0(ぼかさない)です。

とりあえずゼロ据え置き(LOD無視)で良いか。ただ、まだエラーになる:

$ cgc -ogles -profile arbfp1 23a.frag
23a.frag
(0) : error C6007: Constant register limit exceeded; more than 32 constant registers needed to compiled program

... DX8世代のハードウェアにはシェーダが複雑すぎるようだ。無限のリソースを仮定して無理矢理コンパイルしてみると:

$ cgc -ogles -profile arbfp1 -po MaxLocalParams=9999 23a.frag
...
#var half _GI_Intensity :  : c[96] : -1 : 1
...
#const c[103] = 0.074279785 -0.2121582 1.5703125
PARAM c[104] = { program.local[0..96],
                { 1, -1, 0, 0.41674805 },
                { 1.0546875, -0.054992676, 0.5, 2 },
                { 0.29907227, 0.58691406, 0.11401367, 3 },
                { 10, 0, -3, 0.049987793 },
                { 0.0010004044, 0.00010001659, -10, 11 },
                { -2, -0.5, 3.140625, -0.018722534 },
                { 0.074279785, -0.2121582, 1.5703125 } };
# 453 instructions, 13 R-regs

圧巻のパラメタ104個、453命令。。手元の環境(Intel内蔵GPUのOpenGL)では MAX_PROGRAM_LOCAL_PARAMETERS_ARB = 512 、 MAX_PROGRAM_ALU_INSTRUCTIONS_ARB = 1447だったので一応収まりはするが、当時の最小値はそれぞれ 24 と 48 なので全然足りないということになる。

一緒に出てきたエラーである ddxddy は多分微分演算で、これは OpenGL ES 2.0の規格でも OES_standard_derivatives 拡張に逃がされている。(こちらは、現在はほぼ100%の実装がサポートしている https://webglstats.com/webgl/extension/OES_standard_derivatives )。

頂点テクスチャフェッチ(VTF)

頂点シェーダのコンパイルでは頂点テクスチャフェッチ処理が失敗する。この機能はDirectX9世代では当時のATIも実装していなかったので、それより微妙に古いARB Assemblyではできなくても不思議ではない。

$ cgc -ogles -profile arbvp1 28.vert
28.vert
28.vert(47) : error C3004: function "vec4 texture2DLod(sampler2D, vec2, float);" not supported in this profile
28.vert(68) : error C3004: function "vec4 texture2DLod(sampler2D, vec2, float);" not supported in this profile
100 lines, 2 errors.

...が、これってそもそもOpenGL ES2でもオプションだし今でも Mali400 のような非サポートGPUは存在するので前提としては厳しいものがある気はする。

エラーになっている行はそれぞれ、

    u_xlat5.x = texture2DLod(_Outline_Sampler, u_xlat5.xy, 0.0).x;
...
    u_xlat3.xyz = texture2DLod(_BakedNormal, u_xlat3.xy, 0.0).xyz;

前者は アウトライン強弱調整用テクスチャ 。じゃあこの機能を使わないなら不要だな。

後者は ベイクドノーマル の実装とみられる。これも不要なはず。

※注意:この方式による頂点法線の調整は、バーテックスシェーダー側で行われますので、適用される頂点数にそのまま依存します。 つまり、ピクセルシェーダー側のように頂点法線間で補正するものではありませんので、注意してください。

たしかに頂点シェーダで行われているな。。

$ cgc -ogles -profile arbvp1 28a.vert
...
# 66 instructions, 6 R-regs

これらは割と常識的なサイズに収まった。

かんそう

ARB Assemblyに立ち返るというアイデアは我ながら悪くないと思うんだけど、現状処理系が cgc しか無い。KhronosのSPIR-V処理系( https://github.com/KhronosGroup/glslang )はそもそもWebGL1のシェーダを処理できず、GLES 3.0のシェーダに変換する必要がある。更に(当然だけど)SPIR-VからARB assemblyを出力するバックエンドも無いのでその辺は自作する必要がある。なるべくならココも作りたくないんだけど良い代替がない。

Spector.jsは便利だけど、いちいちキャプチャを取るのは面倒なのでwebgl-worker( https://github.com/kripken/webgl-worker )みたいなAPI proxyを用意して実タイトルを遊んでみたりconformanceを通しながら調整した方が良い気はする。当のBabylon.jsは自前のnative実行環境としてWebGLではなく bgfx を選択している( https://github.com/BabylonJS/BabylonNative )のはちょっと面白い。

WebGLやWebGPUしか実行できない "手抜きGPU" コアは割と需要は有る気はしている。WebGLやWebGPU自体が、各社のGPU実装やAPIのセキュアな最大公約数として成立するようにデザインされているのでKhronosの仕様ベース仕様 -- OpenGL ES や Vulkan -- を完全に実装するよりも多分コンパクトになるし、同じプログラムが他ハードウェアでも同様に動作するというのは良い特徴になるのではないだろうか。

例えばGoogleは(パフォーマンスAPIである)Vulkanに邁進していて( https://xdc2019.x.org/event/5/contributions/576/attachments/441/695/SwiftShader_Lightning_Talk.pdf )、次のDirectXが出てきたらどうすんだろうという気持ちになる。実際にはVulkanはWebGPUにはなれなかったように、抽象に耐えられなくなるポイントがどこかで来るかもしれない。

大昔のIntel内蔵GPU (GMA900) がそうだったように、ピクセルシェーダだけをハードウェア実装し、頂点シェーダはCPUに残すような実装は現代的に取り得るだろうか。ピクセルシェーダは精度制約が緩く、かつ、固定機能(ステンシルやZ等)に近いところに置く必要があるのでどうやってもハードウェア実装のメリットには勝てないと思うが、頂点シェーダのコードは、(少くともゲームやWebでは)あまり活用されない傾向にある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スマホ用VRに6DoF機能やハンドトラッキング機能を実装してみた

(Unityアセットやコードはこちら


皆さんこんにちは。

早速ですが、VR(バーチャルリアリティー)は勿論ご存じですよね?
ゴーグルを付けてやる、あれです。

今では、ゴーグルやコントローラーの位置を認識して、自由に動き回れる6DoFという機能があります。
また、最近ではOculus Questがハンドトラッキングに対応した、ということも聞きます。

しかし、このようなVRは、OculusやViveといった、専用のゴーグルが必要で、そのために手軽に試せない、という印象があると思います。
私も実は持っていません。

しかし、スマホをはめるだけで出来る簡易的なゴーグルも存在します。
今では100均でも売っているそうです。
これであれば、安い出費で手軽にVRが体験できる、ということです。
ただ、現状では6DoFやハンドトラッキングに対応したものがほとんどありません。

ということで、実際に作ってみました。
といっても、一から作るのは私には無理なので、既存の技術を流用します。
開発環境はUnityで、現在はAndroidのスマホのみに対応しています。

まず、AndroidスマホにはARCoreという機能があります。
その名の通り、ARに使われるもので、これを使えばスマホのカメラの内容から、ゴーグルの位置を認識することができるようになります。
これによって、6DoFのように、自由に動き回れるようになります。
これは実は過去にやったことがありますが、新しいバージョンのUnityには対応できていなかったので、AR Foundationを使う形で実装し直しました。

次に、ハンドトラッキングを実装するためにMediaPipeというものを使いました。
これは機械学習のモデルを利用するのに使うライブラリだとのことですが、そのサンプルの1つにハンドトラッキングがあるので、それを使いました。
ただ、Unityではそのままでは使えないので、それで実行できるように一部変更しました。
ゴリ押し気味に実装したので、性能が若干落ちたような気もしますが、なんとかなってます。

この2つを使うことで、
Qiita_1.gif

このように自由に動き回ったり、

Qiita_2.gif

手を認識させて動かしたりすることが出来るようになりました。

前述の通り、ARCoreに対応したAndroidスマホと、スマホVR用のゴーグルがあれば、高価なゴーグルが無くとも試すことができます。

ちなみに、このハンドトラッキングで、女の子のおっぱいを触れたり、スカートをめくったりすることが出来るアプリを開発しましたが、見事にリジェクトされました。
公開できればネタとして最高だと思っておりましたが、仕方がないですね。

また、Githubでアセットやコードを公開しているので、6DoFやハンドトラッキングといったこれらの機能を使いたいという方は、ぜひ使ってみてください。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

unity Coroutine

using UnityEngine;
using System.Collections;

public class Sample : MonoBehaviour
{
    private bool update;

    private void Start()
    {
        update = false;
        StartCoroutine("ShowMessage");
    }

    private void Update()
    {
        if (update)
        {
             Debug.Log("Now update.");
        }
    }

    private IEnumerator ShowMessage()
    {
         Debug.Log("GameStart!");
         yield return new WaitForSeconds(3);

         update = true;
         yield return null;
    }
}

解説。
StartCoroutine("メソッド名")
これで実行したいメソッドをコルーチン として実行できる。

メソッドの戻り値はIEnumeratorにする。
yield return new WaitForSeconds(秒数);
これでその部分で秒数分止めることができる。

yield return null;
これでこのメソッドが終わりであることを宣言している。

実行の流れとしては、
1.Startが実行される。
まずupdateフラグをオフにする。
次にメッセージをコンソールに表示するメソッドを実行する
2.1のStartCoroutineによりShowMessageに入る。
まずGameStart!のメッセージがコンソールに表示される。
yield return new WaitForSeconds(3)
ここで一旦メソッド から抜けてその後の部分が実行される

3.2から3秒経過したらyield return new WaitForSeconds(3)より後の部分が勝手に実行される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

unity [SerializeField]

public class Sample : MonoBehavior
{
    [SerializeField]
    private bool hogeFlag;
}

上記の属性を記述するとアクセス権限がprivateの変数でもエディタのinspectorに表示されるようになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity+Googleスプレッドシート+GASでサーバーレスのデータベースシステムを実現する?

前書き

unity1weekをきっかけに、Unity+Googleスプレッドシート+GASで簡易ランキング機能を作ってみました。この仕組みで、ランキングだけではなく、任意のデータをアップロード・ダウンロードできれば、一応汎用的なデータベースとして使えるのではないかと思いました。例えばゲームのマスタデータをGoogleスプレッドシートに保存することで、アプリのバージョン更新なしでゲームの調整ができてまうとか、ゲームの最新バージョンをGoogleスプレッドシートに書き込んで、アプリ起動時にそれを取得して古ければ強制アップデートポップアップを出すとか、さらにチャットやユーザー情報の管理もGoogleスプレッドシートでやりとりするなど、ユースケースがどんどん湧いてきます。
ということで、実装してみました。

使い方

プロジェクトはUmbrella(トトロなので)という名でGithubに公開しました。最新パッケージのダウンロードはこちら。ちなみに、Umbrellaには単純なデータ通信管理システムDatabase以外に、簡易ランキングシステムRankingも含まれています。興味ある方はぜひ合わせていじってみてください。

GAS側

  1. 新しいGoogleスプレッドシートを作成する。
  2. メニューのツールからスクリプト エディタをクリックする。
  3. Assets/Umbrella/Database/Database.gsの内容をコード.gsにコピーする。
  4. メニューのファイル > 保存でプロジェクトに名前をつけて保存する(保存にちょっと2、3秒ぐらい掛かるかも)。
  5. メニューの公開 > ウェブアプリケーションとして導入...をクリックする。
  6. ウェブアプリケーションとして導入のポップアップにて、次のユーザーとしてアプリケーションを実行に自分のアカウントを、アプリケーションにアクセスできるユーザー全員(匿名ユーザーを含む)を設定する。
  7. 導入をクリックして、現在のウェブアプリケーションのURLの下に書いてあるURLをコピーする。
  8. 「認証が必要です」のポップアップが出たら@zk_phiさんの記事を参考にして認証を行ってください。

Unity側

  1. Assets/Umbrella/Database/DatabaseManager.prefabをデータをやりとりしたいシーンのヒエラルキーにドラッグ&ドロップする。
  2. プレハブインスタンスのインスペクターから、App URLのフィールドに先ほどコピーしたGASのウェブアプリケーションURLをペーストする。
  3. Default Sheetフィールドに使いたいGoogleスプレッドシートのデフォルトシート名を入力する。
  4. スクリプト内で、データを送信したい場合はDatabaseManager.Instance.SendDataAsync(data, handleResponseCallback, sheetName)を呼び、データを取得したい場合はDatabaseManager.Instance.GetDataAsync(key, handleResponseCallback, sheetName)を呼ぶ。また、メソッドの前にyield returnを付ければデータ取得後の処理(handleResponseCallback)の実行完了まで待つことができる。
  5. 具体的な使い方はサンプルシーンとスクリプトを参照してください。

デモ

  • Googleスプレッドシートにデータを送信します。デモではデータ名と値のペアで複数データを送信しています。
    send_data.gif

  • Googleスプレッドシートにあるデータを更新します。デモではデータ名を指定して新しい値を送信しています。
    update_data.gif

  • 他のクライアントとしてデータを送信します。Umbrellaではクライアントを識別するユニークIDをUnity側で生成してPlayerPrefsに保存しているため、PlayerPrefsをクリアしないまま送信すると既存のデータを上書きすることになります。
    send_another_data.gif

  • Googleスプレッドシートにあるデータを取得します。デモではデータ名のリストで複数のデータを取得しています。
    get_data.gif

  • セル参照で範囲内のデータを一気に取得する方法もあります。
    get_data_by_cell.gif

実装の抜粋

GAS側の処理

GASはUnityから送ってきたデータに基づき、スプレッドシートの中身を更新します。

function doPost(e) {
  var request = e.parameter;
  var method = request[CONST.Method];

  if(method == CONST.SaveData){
    return saveData(request);
  }else if(method == CONST.GetData) {
    return getData(request);
  }

  return ContentService.createTextOutput("Error: Invalid method");
}

saveDatagetDataの実装詳細はここでは省略しますが、GASのAPIコールはコスト高いため、処理速度を高めにはできる限りAPIコールの回数を減らす必要があります。例えばセルデータの取得で、各セルでsheet.getRange().getValue()の代わりに、予めvar data = sheet.getDataRange().getValues()で指定範囲のセルデータを一括で配列に格納し、後で配列から値を取るなどの策が考えられます。また、データの書き込みがある場合、排他処理(ロック)を入れる必要がありますが、一行のデータをまとめて一括でappendRow()を使えばセル単位でロックをかける手間をなくすテクニックもあります。appendRow()は不可分操作(Atomic Operation)なので排他処理が要らないからです。

UnityからGASへの送信

簡易のデータベース機能なので、特に通信の仕様とかは決めなく(暗号化??)、完全にJSON形式で送信しています。JSON解析は軽量のMiniJsonを導入しています。

public CustomYieldInstruction SendDataAsync(MonoBehaviour context, string methodName, string sheetName, Dictionary<string, object> data, Action<object> handleResponse = null)
{
    var strData = Json.Serialize(data);

    var formData = new List<IMultipartFormSection>();
    formData.Add(new MultipartFormDataSection("method", methodName));
    formData.Add(new MultipartFormDataSection("sheet", sheetName));
    formData.Add(new MultipartFormDataSection("data", strData));

    bool complete = false;
    context.StartCoroutine(CT_SendData(formData, status => complete = status, handleResponse));

    return new WaitUntil(() => complete);
}

WWWFormがLegacyになったので、IMultipartFormSectionでフォームデータを作成しています。フォームデータにGAS側で呼び出したいメソッド名、使いたいシート名とデータ内容を入れています。また、外部でyield returnをつけて待たせられるように、返り値のタイプをCustomYieldInstructionにしてcompleteがtrueになるまで処理を止めることを可能にしています。

UnityWebRequestPostメソッドでフォームデータを送信しています。

private IEnumerator CT_SendData(List<IMultipartFormSection> formData, Action<bool> updateStatus, Action<object> handleResponse = null)
{
    updateStatus(false);

    var www = UnityWebRequest.Post(_appURL, formData);

    Debug.Log("<color=blue>[GSSDataService]</color> Start sending data to Google Sheets.");

    yield return www.SendWebRequest();

    if (www.isNetworkError || www.isHttpError)
    {
        Debug.LogError($"<color=blue>[GSSDataService]</color> Sending data to Google Sheets failed. Error: {www.error}");
    }
    else
    {
        Debug.Log("<color=blue>[GSSDataService]</color> Sending data to Google Sheets completed");
        try
        {
            var response = Json.Deserialize(www.downloadHandler.text);
            string message = response as string;
            if (message != null && message.Contains("Error")) Debug.LogError($"<color=blue>[GSSDataService]</color> Getting data from Google Sheets failed. {message}");
            else handleResponse?.Invoke(response);
        }
        catch (InvalidCastException e)
        {
            Debug.LogError($"<color=blue>[GSSDataService]</color> Parsing result from Google Sheets failed. Error: {e.Message}");
        }

    }

    updateStatus(true);
}

返ってきた結果にError文字列(GAS側で入れている)が含まれたらエラーログを書き出し、なければ結果を処理するhandleResponseメソッドを呼び出します。

後書き

Unity+Googleスプレッドシート+GASでサーバーレスの簡易データベース機能を作ってみました。当然いくつか問題もあります。

  • 完全JSON形式でデータのやりとりをしているため、複雑のデータ構造に対応できない(自分でシリアライザとデシリアライザを書くなどの工夫が要る)。
  • 安全性一切考えていない(Google神がいい感じにしてくれるはず)。
  • 負荷検証や処理速度を計測・比較していないので不明(使った肌感だと耐えられレベルの遅延)。
  • そもそもGoogleのサービスに制限がある。URL Fetch callsだと、無料のGmailアカウントで1日2万回までしか呼べないので、大規模や非常に頻繁な通信に向いていない。

しかしながら個人プロジェクトレベルのものとしては十分機能できるのではないかと思います。何より、Googleスプレッドシートならではの機能が使えて、直接データを一目瞭然で見たり、気軽にデータを修正したりすることができる点から、普通のSQLデータベースよりも便利かもしれません?(?)

おまけ

Umbrellaにランキング機能もついているので、それの使い方も紹介します。
1. Assets/Umbrella/Ranking/RankingManager.prefabをランキングを表示したいシーンのヒエラルキーに置いておく。
2. Assets/Umbrella/Ranking/RankingSettings.assetのインスペクターから、App URLフィールドにGASのウェブアプリケーションURLをコピーする。
3. Ranking Request Settingsフィールドに、ランキングの種類ごとで配列に要素を入れていく。Ranking Nameはランキングの名前で、Ranking Numberは上位何位まで取得するかを指定し、Order Byは昇順(ASC)か降順(DESC)を選べる。
4. スクリプト内でRankingManager.Instance.SendScoreAsync(playerName, score, handleResponseCallback, rankingRequestIndex)を呼んでスコアを送信し、RankingManager.Instance.GetRankingListAsync(handleResponseCallback, rankingRequestIndex)でランキングリストを取得できる。同様に、メソッドの前にyield returnを付ければデータ取得後の処理(handleResponseCallback)の実行完了まで待つことができる。
5. 具体的な使い方はサンプルシーンとスクリプトを参照してください。

  • Googleスプレッドシートにスコア送信します。
    send_score.gif

  • Googleスプレッドシートにあるスコアを更新します。
    update_score.gif

  • Googleスプレッドシートのランキングリストを取得します。
    get_ranking.gif

参考

  1. UnityのWebGL出力に簡単に無料でグローバルランキングを実装できる仕組みを考えてみた
  2. GAS で「一部のスコープへのアクセス権限がありません」と怒られたときの対処法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

音を再生する

unityでは同じシーンの中に
音を再生する場所であるAudio Source
音を聞く場所であるAudio Listener
が必要になります

片方だけでは音が再生されないので注意が必要です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityEditorでの言語設定エミュレーション用のスクリプト

やりたいこと

Unityでは、Application.systemLanguageでシステムの言語設定が取得できます。
UnityEditorで実行中はApplication.systemLanguageの値はOSの設定と同じになると思います。(※1)
しかし、このままだと言語を切り替えたときの動作の確認がやりにくいです。(検証のたびにOSの言語設定を変えないといけないため)
そのため、UnityEditorで実行した時はエディタで設定した言語を読み取り、ビルドしたアプリではシステムの言語設定を読み取るようなスクリプトを作成しました。

※1 macOS Catalina / Unity2019.3.0f3 環境ではOSが日本語設定でもなぜかEnglishになります。

スクリプト

using System;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace HiiroHitoyo
{
    #if UNITY_EDITOR
    public class EmulatableLanguageSettings : SettingsProvider
    {
        static readonly string emulatedLanguageKey = "EmulatedLanguageKey";
        public EmulatableLanguageSettings( string path, SettingsScope scope  ): base( path, scope ) { }

        [SettingsProvider]
        private static SettingsProvider Create()
        {
            var path        = "Project/Language Emulation";
            var provider    = new EmulatableLanguageSettings( path, SettingsScope.Project );
            provider.keywords = new []{ "Language"};
            return provider;
        }
        public override void OnGUI( string searchContext )
        {
            var currentLanguage = emulatedLanguage;
            var lang = (SystemLanguage) EditorGUILayout.EnumPopup("Language", currentLanguage);
            EditorPrefs.SetInt(emulatedLanguageKey, (int) lang);
        }

        static public SystemLanguage emulatedLanguage => (SystemLanguage) Enum.ToObject(typeof(SystemLanguage), EditorPrefs.GetInt(emulatedLanguageKey, (int)Application.systemLanguage));
    }
    #endif

    public class EmulatableLanguage
    {
        #if UNITY_EDITOR
        static public SystemLanguage systemLanguage => EmulatableLanguageSettings.emulatedLanguage;
        #else
        static public SystemLanguage systemLanguage => Application.systemLanguage;
        #endif
    }
}

使い方

設定言語の読み取り方法

Application.systemLanguageの代わりに、EmulatableLanguage.systemLanguageを呼び出してください。

using HiiroHitoyo

public class Example
{
    public void test()
    {
        Debug.Log("Language = " + EmulatableLanguage.systemLanguage);
    }
}

言語設定方法

Edit -> Project Settings から Language Emulationタブを選択し、プルダウンから設定したい言語を選択したください。

注意事項

設定値をEditorPrefsで保存しているので、設定値はプロジェクトをまたいで変更されます。
設定をプロジェクト内で留めたい場合はEditorUserSettingsで保存するなどしてください。

動作確認環境

Mac OS 10.15.2
Unity 2019.3.0f3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む