Kilimanjaro Warehouse

WEBとかゲーム開発のことについて書きます。

SteamVR: ビルドしたゲームでコントローラーのバインドが適用されないときの対処

UnityでVRゲームを作っている際、コントローラーがエディター上では想定通りに操作できるのに、ビルドしたアプリではうまく動作しないという状況に陥りました。

調べてみると、カスタムしたコントローラーのバインド設定が反映されていないことが原因のようでした。

日本語で探しても見つからなかったので、解決手順を簡単な翻訳とともにメモとして残します。

解決手順
  1. Unitiy Editor -> Window -> Steam VR Input -> "Open Binding UI" で "Developer Binding for Vive Controller ..." を開く(既にやっているはず)
  2. そのゲームのバインドの設定をする
  3. "Replace dafault binding"を押す。これは開発しているゲームに保存しているbindings_vive_controller.jsonを置き換える
  4. ゲームをビルドする
  5. バインドの設定は、ゲームのexeファイルがある場所にエクスポートされ、bindings_vive_controller.jsonとして保存される
  6. ゲームを起動すれば、コントローラーが想定通りに動くはず


引用元:
steamcommunity.com

原因?

bindings_vive_controller.jsonを書き換える前は、バインドの設定は以下のパスに保存されているようです。

C:\Program Files (x86)\Steam\userdata\youruserid#\GameID#

引用元:
steamcommunity.com

おそらくですがビルドされたアプリは、Editorで開発していたものとGameIDが変わってしまうので、バインドの設定が適用されないのかなと思います。
どちらにせよ、バインドの設定はローカルで自分の環境でしか適用されないはずなので、他人に配布する際はbindings_vive_controller.jsonを書き換える必要がありそうです。

実行環境:
OS: Windows 10
Editor: 2019.4.8f Personal
SteamVR Unity Plugin: v2.5 (sdk 1.8.19)

VRMを使ったアプリのサンプル集的なものを作りました

GitHubのリンク
github.com

デモ
kilimanjaro-a2.github.io

最近VRMを使ってWebGL向けのアプリを作る人が増えているようで、

前回の記事で書いたような、VRMを使用したアプリ作成の解説の需要が増しているようです。
kiliware.hateblo.jp

なので、簡単なサンプルをまとめたプロジェクトを作成して、GitHubで公開することにしました。

WebGL上からVRMを読み込む方法、表情やポーズ変更、メタ情報表示などの基本的な操作に加えて、シンプルなゲームも入っています。

f:id:kilimanjaro-a2:20200506183606p:plain

MITライセンスなので自由に改造して何か作ってください。

Unity: WebGLビルドでVRMファイルを読み込む(UnityWebRequest版)

UnityのWebGLビルドでVRMファイルを読み込む方法については、
既にこちらの記事で紹介されています。
qiita.com

上記の記事ではObsoleteなWWWを使用していたので、
単純にUnityWebRequestを使った形へと書き換えたものを作ってみました。
ただそれだけの記事です。

コード

using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Networking;
using VRM;

public class Sample : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void FileImporterCaptureClick();

    public void OnButtonClicked()
    {
        #if UNITY_EDITOR
            Debug.Log("WebGLビルドで試してください");
        #elif UNITY_WEBGL
            FileImporterCaptureClick();
        #endif
    }

    public void FileSelected(string url)
    {
        StartCoroutine(LoadJson(url));
    }

    private IEnumerator LoadJson(string url)
    {
        using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
        {
            yield return webRequest.SendWebRequest();

            if (webRequest.isNetworkError)
            {
                Debug.LogError("ネットワークエラー");
            }
            else
            {
                LoadVRMFromBytes(webRequest.downloadHandler.data);
            }
        }
    }

    public void LoadVRMFromBytes(Byte[] bytes)
    {
        var context = new VRMImporterContext();
        try {
            context.ParseGlb(bytes);
            var meta = context.ReadMeta(true);
            context.Load();

            var model = context.Root;
            model.gameObject.name = meta.Title;

            context.ShowMeshes();

        } catch(Exception e) {
            Debug.LogError(e);
        }
    }
}

プロジェクト

github.com


実際にビルドしたもの

kilimanjaro-a2.github.io

Node.js: 関数の中身を見る方法

node.js上でconsole.log()を使って関数の中身を見ようとすると、

console.log(hoge)
// [Function: hoge]

のように名前が返ってくるだけで、中身をみることができません。

中身をみるには、関数にtoString()をつければ良いようでした。

console.log(hoge.toString())
// function hoge() {
//     ~~~~処理~~~~
// }

参考: console.log javascript [Function] - Stack Overflow

VuePressでホットリロードが効かないときの対処法

vuepress dev

VuePressでは上記のコマンドでローカルサーバーを立てることができ、
ファイルに変更があった場合、ブラウザのリロードなしに自動的に更新が適用されます。

しかし、自分の環境(WSL1)ではこのホットリロードが効きませんでした。
その場合、以下のような記述をconfig.jsに記述することで解決しました。

module.exports = {
  host: "localhost"
}

情報元:Automatic Reload? · Issue #220 · vuejs/vuepress · GitHub


config.jsはVuePressの全般的な設定を書いておくファイルで、
慣習的にはdocs/.vuepress以下に配置されます。

hostプロパティではdevサーバーで使うホストを設定できます。
値はデフォルトで'0.0.0.0'になっているようですが、
今回は'localhost'と設定を変更することで正常にホットリロードが効くようになりました。

情報元:
Configuration | VuePress
Config Reference | VuePress

Unity: Interactive/Autodesk Interactive.shadergraph Null returned.の解消法

以下のようなエラーが出た時の解決法です。

Exception: Cannot load. Incorrect path: Packages/com.unity.render-pipelines.lightweight/Shaders/Autodesk Interactive/Autodesk Interactive.shadergraph Null returned.
UnityEngine.Rendering.ResourceReloader.Load (System.String path, System.Type type, System.Boolean builtin) (at Library/PackageCache/com.unity.render-pipelines.core@6.9.2/Runtime/Utilities/ResourceReloader.cs:138)
UnityEngine.Rendering.ResourceReloader.SetAndLoadIfNull (System.Object container, System.Reflection.FieldInfo info, System.String path, System.Boolean builtin) (at Library/PackageCache/com.unity.render-pipelines.core@6.9.2/Runtime/Utilities/ResourceReloader.cs:147)
UnityEngine.Rendering.ResourceReloader.ReloadAllNullIn (System.Object container, System.String basePath) (at Library/PackageCache/com.unity.render-pipelines.core@6.9.2/Runtime/Utilities/ResourceReloader.cs:45)

まず、PackageManagerからShader GraphとCore RP Libraryを削除します。
そして、High Definition RPもしくはLightweight RPの、
どちらか使用しているRender Pipelineを削除します。

その後、先ほど削除したPackageの最新版をインストールして、
Unityを再起動するとエラーが解消されます。

answers.unity.com

JavaScript: parseInt(0.000001)が0を返し、parseInt(0.000001)が1を返す理由

JavaScriptのparseIntというメソッドで、
0.000001(10の-6乗)を引数にすると、返り値は0になるのに、
0.0000001(10の-7乗)は1が返ってくるという、
意味不明な挙動がTwitterで話題になっていました。

parseInt(0.000001) // 0

parseInt(0.0000001) // 1

この挙動について調べてみました。


TL;DR 要約

  • parseIntは有効でない文字があると、それ以降の文字を無視する
  • 小数点以下7桁以下の数値は、指数表記に変換される

上の仕様によって、この不可解な挙動が説明できます。


解説

parseIntは第一引数に変換するstring型の文字列、第二引数に基数を取ります(省略可)。
string型ではないものが第一引数に渡されると、内部でstring型に変換されます。
そして、数字と認識されない文字があった場合、それ以降の文字は無視されます。

parseInt(0.000001)

小数点が含まれる数字を第一引数に取る場合、小数点は数字として認識されません。
よって、"0.000001"の".000001"は無視され、先頭の0のみが返されます。


そして、0.0000001が1を返す方については、
小数点以下7桁以下の数値は指数表記に変換されることが原因のようです。

parseInt(0.0000001)

0.0000001は内部的にstringに変換される際、"1e-7"のような指数表記になります。
"e-7"は有効な数字ではないので無視され、先頭の1のみが返されます。


このような理由により、
parseInt(0.000001)は0を返し、parseInt(0.000001)は1を返すようでした。


どのような経緯で指数表記の閾値が小数点以下7桁になったのかはわかりませんが、
この閾値ECMAの仕様で決められているようです。
ECMAScript Language Specification - ECMA-262 Edition 5.1


また、ChromeJavaScript実行エンジンであるV8のテストケースにも、
上記の挙動が正しい動作としてテストが通るように書かれています。

PASS parseInt(Math.pow(10, -6)) is 0
PASS parseInt(Math.pow(10, -7)) is 1

v8/parseInt-expected.txt at a28c760ef01d2b9749c26d96aa5278a736ad4591 · v8/v8 · GitHub


結論

一見意味不明な挙動に見えるが、
しっかり調べると動きの理由があるようでした!