20200917のUnityに関する記事は5件です。

ループ中にリストの要素を削除する場合の注意点

リストをループで回している中で、そのリストの要素を削除する場合は、ループを逆順で回しましょう。

NG
        for (int i = 0; i < enemyList.Count; i++) {
            if (!enemyList[i].IsAlive) {
                enemyList.RemoveAt(i);
            }
        }
OK
        for (int i = enemyList.Count - 1; i >= 0; i--) {
            if (!enemyList[i].IsAlive) {
                enemyList.RemoveAt(i);
            }
        }

なぜ逆順にしなければならないのか?を示したものが以下の図になります。NG側では、要素Cを削除した際に後ろにある要素が一つ前に詰められる影響で、要素Dが正しく処理されない問題が発生しています。

figure.png

ちなみに、リストの要素を削除したいけどループ中に処理する必要はない時にはRemoveAllが便利です。

        enemyList.RemoveAll(enemy => !enemy.IsAlive);

参考リンク

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

StartCoroutine での切り替わりのタイミング

環境

Unity2017.4.1f1

コルーチン呼び出しで、関数が切り替わるタイミングのメモ。

    void Start () {
        int twork = 0;
        Debug.Log("Start1 twork="+twork);
        StartCoroutine( Test00( (T) => { twork = T; } ) );
        Debug.Log("Start2 twork="+twork);
    }

    public IEnumerator Test00(Action<int> func)
    {
        func(99);
        yield return null;
        func(88);
        Debug.Log("Start3 ");
    }

上記のようなコードを作成して実行してみた。
なんとなく、コルーチン処理は登録後、即座に実行はされていないと思っていた。
つまり、
Start2 twork=0
となるのではないか?

結果

Start1 twork=0
Start2 twork=99
Start3

となった。
なるほど、StartCoroutineが呼ばれた後は、その関数に遷移し、yield return が呼ばれるまで処理を行い
呼ばれた後に呼び先の関数の処理が継続されるのね。

個人的な備忘録です。

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

Python + Unity 強化学習(学習編)

前回の環境構築後に実際にUnityで強化学習を行っていく!
前回の記事はこちら、https://qiita.com/nol_miryuu/items/32cda0f5b7172197bb09

前提条件

Unityの基礎知識が多少必要です(オブジェクトの作り方・名前の付け方)

目的

青い球(AI Agent)が床から落ちずに黄色い箱(Target)に素早く近づくことができるようにAIを学習させる

構成

状態:Vector Observation(サイズ=8)
・ TargetのX,Y,Z座標の3つ
・ RollerAgentのX,Y,Z座標の3つ
・ RollerAgentのX,Z方向速度の2つ(Y方向には移動しないので除外)

行動:Continuous(サイズ=2)
・ 0:RollerAgentのX方向に加える力
・ 1:RollerAgentのZ方向に加える力

報酬:
・ RollerAgentがTargetに近づいた(RollerAgentとTargetとの距離が0に近づいた)場合,報酬(+1.0)を与え、エピソード完成
・ RollerAgentが床から落ちた(RollerAgentのY方向の位置が0未満になったら)場合、報酬を与えずにエピソード完了

決定:
・ 10ステップごと

強化学習サイクル(1ステップごとに実行されるプロセス)
状態取得 → 行動決定 → 行動実行と報酬取得 → ポリシー更新

学習環境の準備

1.青い球、名前=RollerAgentを配置する

スクリーンショット 2020-09-17 130159.png

2.黄色い箱 名前=Target を配置する

スクリーンショット 2020-09-17 130710.png

3.床 名前=Floor を配置する

スクリーンショット 2020-09-17 130728.png

4.Main Camera : カメラの位置・角度を赤丸のように設定(全体がよく見える位置に調整するため)

スクリーンショット 2020-09-17 131155.png

5.各オブジェクトに色を付けるためにMaterialを作成(Asset>create)

スクリーンショット 2020-09-17 131256.png

6.メニューのWindowからPackage Managerを選択(ML-Agentをインポートする)

スクリーンショット 2020-09-17 131422.png

左上の「+」ぼたんを押してAdd package from diskを選択

スクリーンショット 2020-09-17 131435.png

前回作成したディレクトリに行き、ml-agents/com.unity.ml-agents/package.jsonを選択する

スクリーンショット 2020-09-17 131629.png

7.RollerAgent(青い球)にコンポーネント(機能)を追加する

・ Rigidbody : 物理シミュレーションの仕組み
・ Behavior Parameters : RollerAgentの状態と行動のデータを設定
・ Decision Requester : 何ステップごとに「決定」を要求するかを設定
基本的にステップは0.02秒ごとに実行される。Decision Periodが「5」の場合は5 x 0.02 = 0.1秒ごと、
「10」の場合は10 x 0.02 = 0.2秒ごとに「決定」が実行されるRollerAgentに対しては最終的に下図のように設定する


Rigidbody
スクリーンショット 2020-09-17 140104.png


Behavior Paramenters
・Behavior Name:RollerBall(この名前でモデルが生成される)
・Vector ObservationのSpace Size:8(観察する状態の種類)
・Space Type:Continuous(行動の種別)
・Vector ActionのSpace Size:2(行動の種類)
スクリーンショット 2020-09-17 140130.png


Decision Requester
スクリーンショット 2020-09-17 140311.png

8.RollerAgents.csスクリプトを作成

・void Initialize()・・・エージェントのゲームオブジェクト生成時に1回だけ呼ばれる
・OnEpisodeBegin()・・・エピソード開始時に呼ばれる
・CollectObservations(VectorSensor sensor)・・・エージェントに渡す状態データを設定する
・OnActionReceived(float[] vactorAction)・・・決定された行動を実行し、報酬取得とエピソード完了を行う

RollerAction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;

public class RollerAgent : Agent
{
    public Transform target;
    Rigidbody rBody;

    public override void Initialize()
    {
        this.rBody = GetComponent<Rigidbody>();
    }

    // エピソード開始時に呼ばれる
    public override void OnEpisodeBegin()
    {
        if (this.transform.position.y < 0) // RollerAgent(球)が床から落下している時に以下をリセット
        {
            this.rBody.angularVelocity = Vector3.zero; // 回転加速度をリセット
            this.rBody.velocity = Vector3.zero;         // 速度をリセット
            this.transform.position = new Vector3(0.0f, 0.5f, 0.0f); // 位置をリセット
        }
        // Target(キューブ)の位置をリセット
        target.position = new Vector3(Random.value * 8 - 4, 0.5f, Random.value * 8 - 4);
    }

    // エージェントに渡す観察データ(8項目)を設定する
    public override void CollectObservations(VectorSensor sensor)
    {
        sensor.AddObservation(target.position); // Target(キューブ)のXYZ座標
        sensor.AddObservation(this.transform.position); // RollerAgentのXYZ座標
        sensor.AddObservation(rBody.velocity.x); // RollerAgentのX軸方向の速度
        sensor.AddObservation(rBody.velocity.z); // RollerAgentのZ軸方向の速度
    }

    // 行動実行時に呼ばれる
    public override void OnActionReceived(float[] vectorAction)
    {
        // RollerAgentに力を加える
        Vector3 controlSignal = Vector3.zero;

        controlSignal.x = vectorAction[0]; // ポリシーによって決定された行動データをセットする 
                                                              // vectorAction[0]は X方向 に加える力(-1.0 〜 +1.0)
        controlSignal.z = vectorAction[1]; // ポリシーによって決定された行動データをセットする
                                                              // vectorAction[1]は Y方向 に加える力(-1.0 〜 +1.0)

        rBody.AddForce(controlSignal * 10);

        // RollerAgentとTargetとの距離を測定
        float distanceToTarget = Vector3.Distance(this.transform.position, target.position);

        // RollerAgentがTargetの位置に到着したとき
        if(distanceToTarget < 1.42f)
        {
            AddReward(1.0f); // 報酬を与える
            EndEpisode(); // エピソードを完了する
        }

        // RollerAgentが床から落下したとき
        if(this.transform.position.y < 0)
        {
            EndEpisode(); // 報酬を与えずにエピソードを完了する
        }
    }
}

9.RollerAgentのプロパティを設定

Max Step:エピソードの最大ステップ数、エピソードのステップ数が設定値を超えるとエピソード完了となる
Max Stepを1000、Target欄に黄色の箱「Target」を選択

10.ハイパラメータ設定ファイルの作成

・ml-agents/config/にsampleディレクトリ作成
・その中にRollerBall.yamlファイルを作成、ファイル内容は以下のとおり

ハイパーパラメータ(訓練設定ファイル 拡張子.yaml[ヤムルと読む] )
- 学習に利用するパラメータ
- 人間が調整する必要がある
- 強化学習アルゴリズム(PPO/SAC)ごとに設定項目が異なる

RollerBall.yaml
behaviors:
  RollerBall:
    trainer_type: ppo
    summary_freq: 1000
    hyperparameters:
      batch_size: 10
      buffer_size: 100
      learning_rate: 0.0003
      learning_rate_schedule: linear
      beta: 0.005
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3
    network_settings:
      normalize: true
      hidden_units: 128
      num_layers: 2
      vis_encode_type: simple
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    keep_checkpoints: 5

RollerAgentの学習開始

前回のQittaで作成した仮想環境を動かす

terminal
poetry shell

ml-agentsディレクトリで以下のコマンドを実行する
terminal:terminal
mlagents-learn config/sample/RollerBall.yaml --run-id=model01

最後のmodel01は新たに訓練するたびに別名をつける

terminal
Start training by pressing the Play button in the in the Unity Editor.

上のコードがterminalに表記されたらUnityに戻って再生ボタンを押すと実行されます

1.Unityの再生ボタンを押すと訓練が始まる。

Stepが50000ごとにターミナルに情報が表示される。
Mean Reward:平均報酬ポイント・・・値が高くなれば精度が上がる. 1.0になったら訓練終了してしましょう。

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

UnityのWebGLで気づいたことまとめ

UnityのWebGL出力で気づいたことをまとめていきます。
後で追記する予定です。

この記事の内容で、動かなかったところがあればコメント欄で教えて下さい。
一緒に解決しましょう。
解決策が残っていたら、他の人の役にも立つかもしれません。

※ Unity のバージョンによって動作が異なる場合もあるかもしれません。
その点についても情報を頂けると助かります。

Unity側

サンプルファイルの場所

  • Windows: C:\Program Files\Unity\Hub\Editor\20xx.x.xxx\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\lib\*.js
  • Mac: /Applications/Unity/Hub/Editor/20xx.x.xxx/PlaybackEngines/WebGLSupport/BuildTools/lib/*.js

(Unity Hub 導入時)

JS側の関数を宣言する時のテンプレート

using System.Runtime.InteropServices;

public class Hoge {
    [DllImport("__Internal")]
    static extern int JS_Hoge_Create(string url);
}

JSから呼び出せるC#メソッドの宣言テンプレート

using AOT;
using System;

public class Hoge {
    // static でないと呼び出せない
    [MonoPInvokeCallback(typeof(Action<int>))]
    static void HogeCallback(int fuga) {
        UnityEngine.Debug.Log(fuga);
    }
}

ビルドを速くする

結論: どうやっても速くならない

ここに一応、速くする方法は書かれています。

これらの手順を踏まえても、それほど速くできません。

上の URL に書かれていない手順を紹介します。
次の状態でいったん全てをビルド後、

  • Development Build をチェック
  • Scripts Only Build のチェックを外す

2回目のビルドは同じ出力場所に、

  • Scripts Only Build をチェック

でビルドすると、スクリプト以外のところはビルドを省略できるため、少し速くなるようです。
ほぼコンテンツなしで WebGL だけのコードを書いている時は速くなった気がしません。
初回のビルドで Scripts Only Build をチェックしてしまうと、ビルドに失敗します。

そもそもなぜ遅いのかという話はここで色々書かれています。

引用すると、

  1. Compile C# to IL (C#をILにコンパイル。monoのビルドツールが実行される ?)
  2. Strip assemblies (ILのアセンブリから不要なコードを削除)
  3. Convert IL to C++ (ILをC++に変換。IL2CPPが実行される ?)
  4. Compile C++ into WASM. (C++をWebAssemblyにコンパイル。Emscriptenのビルドツールが実行される ?)

これだけの手順でビルドするため、遅くなるようです。
IL2CPP ではなく、 IL2WASM のようなツールが登場すれば改善されるかもしれません。

スタックオーバーフロー等でも同じように困っている人がいるので、おそらく世界中の人がビルドの遅さに困っているのではないかと思います。 "unity webgl build slow" 等で検索すると色んなページが出てきます。

ブラウザ側

コンソールでメモリの中身をチェック

WebInspector の Console 画面で次のように入力すると、 Emscripten 内で確保されたメモリを確認できます。

unityInstance.Module.HEAPU8[0]

HEAPU8の部分には、次のような変数も指定することができます。

名前 説明
HEAP8 Int8Array 符号付き 8bit 整数
HEAPU8 Uint8Array 符号なし 8bit 整数
HEAP16 Int16Array 符号付き 16bit 整数
HEAPU16 Uint16Array 符号なし 16bit 整数
HEAP32 Int32Array 符号付き 32bit 整数
HEAPU32 Uint32Array 符号なし 32bit 整数
HEAPF32 Float32Array 32bit 浮動小数点数
HEAPF64 Float64Array 64bit 浮動小数点数

上記の型は、 TypedArray から派生されていますので、 subarray() 等のメソッドを呼び出すことができます。

メモリ使用量のチェック

Console で次のように入力すると、 Emscripten で確保されたメモリの使用量が分かります。

unityInstance.Module.asmLibraryArg.getTotalMemory()

HEAPxx.buffer.slice() vs HEAPxx.subarray()

どちらも同様に、配列の一部分を配列で抜き出す操作ができますが、速度に差があります。
subarray() のほうが速そうです。

参考URL

unityInstance のメンバ

プロパティ

名前 説明
Module Object Emscripten のモジュール
container HTMLDivElement 表示領域の div 要素
logo HTMLDivElement ロゴ画像の div 要素
progress HTMLDivElement プログレスバーの div 要素?
url string なにかの json ファイルの URL

メソッド

名前 説明
Quit(onQuit) Unity の実行環境を終了?
SendMessage(objectName, methodName, ...) Unity のメソッドを呼び出す
SetFullscreen(fullscreen) true: フルスクリーン, false: 解除
compatibilityCheck(onsuccess, onerror) 互換性チェック?
popup(message, callbacks) 用途不明

SendMessage() は便利に使えるかもしれません。
objectName 引数には、ヒエラルキービューの場所を文字列で入れるようです。

unityInstance.Module のメンバ

Emscripten のドキュメントは次の場所にあります。

Unity で追加されたメンバ (?)

プロパティ

名前 説明
companyName string 会社名
productName string 製品名
productVersion string 製品バージョン
unityVersion Object Unity のバージョン情報
developmentBuild bool 開発用ビルドかどうか (?)
graphicsAPI Array WebGL1, 2のどちらを使うか (?)
splashScreenStyle string スプラッシュ画面のスタイル
useWasm bool WebAssemblyを使っているか (?)
usingWasm bool WebAssemblyを使っているか (?)
canvas HTMLCanvasElement Unity の描画領域の canvas タグ
ctx WebGL2RenderingContext Unity の描画領域の操作用 (?)

メソッド

名前 説明
pauseMainLoop() メインループの停止
resumeMainLoop() メインループの再開
resolveBuildUrl(buildUrl) Buildフォルダ以下の場所を返す
setCanvasSize(width, height, noUpdates) 描画領域のサイズ変更
setWindowTitle(title) ウインドウのタイトルを変更
streamingAssetsUrl() ストリーミングアセットのURL (?)

Console で色々試す時に、 pauseMainLoop() を事前に呼び出しておくと負荷が下がって便利です。

Unityのビルド時に生成されたメインスクリプトの中身を見る

次のコードを入力することで、メインスクリプトの内容が見れるようです。
今のところ詳しくは調べていません。

unityInstance.Module.mainScriptUrlOrBlob.text().then((s) => {console.log(s)})

"mainScriptUrlOrBlob" とあるように、実際には URL が返されるケースがあるのかもしれません。
Blobが返された場合は、上のコードで動きます。

メインスクリプトの表示
Console 画面の、赤で囲んだところをクリックしてもメインスクリプトを見ることができます。表示されたら、全選択してエディタにコピペすると見やすいです。約 25,000 行ほどあります。

メインスクリプトの内容について

システムコール

___syscall から始まる関数で OS のシステムコールのような機能が実装されています。番号が振られていますが、どの番号がどの機能かまでは今のところ調べきれていません。

OS のシステムコールとはどのようなものかというと、例えば WebAssembly 内から擬似的にファイルシステムのような機能を使う仕組みがあります。具体的には fopen() のような C 言語の関数を実行できるようになっています。

WebAssembly 内に実装された関数からシステムコールを呼び出す時は、適宜 JavaScript で実装された外部関数を呼び出すようになっていて、 JavaScript で擬似的に OS の仕組みが動いているかのように見えるようになっています。なぜそのようになっているかというと、実質的に WebAssembly は基本的な (uint8_t, uint16_t のような) 型ごとの演算とメモリの操作ができるだけで、モニタに画像を表示したり、キーボードの入力を受け取ったり、システム時間を取得したりするような機能が実行できないようになっているからです。基本的な演算とメモリ操作以外のほぼ全ての機能の実行には、 JavaScript で実装された外部関数呼び出しが必要になります。そのような関数類が、 Emscripten によって ___syscallxxx という関数で提供されています。最近の WebAssembly では WASI という共通の基盤で OS の機能に相当する部分を実装しようという動きがあります。

lengthBytesUTF8の中身

function lengthBytesUTF8(str) {
    var len = 0;
    for (var i = 0; i < str.length; ++i) {
        var u = str.charCodeAt(i);
        if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023;
        if (u <= 127) {
            ++len;
        } else if (u <= 2047) {
            len += 2;
        } else if (u <= 65535) {
            len += 3;
        } else if (u <= 2097151) {
            len += 4;
        } else if (u <= 67108863) {
            len += 5;
        } else {
            len += 6;
        }
    }
    return len;
}

Pointer_stringifyの中身

function Pointer_stringify(ptr, length) {
    if (length === 0 || !ptr) return "";
    var hasUtf = 0;
    var t;
    var i = 0;
    while (1) {
        assert(ptr + i < TOTAL_MEMORY);
        t = HEAPU8[ptr + i >> 0];
        hasUtf |= t;
        if (t == 0 && !length) break;
        i++;
        if (length && i == length) break;
    }
    if (!length) length = i;
    var ret = "";
    if (hasUtf < 128) {
        var MAX_CHUNK = 1024;
        var curr;
        while (length > 0) {
            curr = String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, MAX_CHUNK)));
            ret = ret ? ret + curr : curr;
            ptr += MAX_CHUNK;
            length -= MAX_CHUNK;
        }
        return ret;
    }
    return UTF8ToString(ptr);
}

jslib ファイルのテンプレート

var LibraryHogeWebGL = {
    $Hoge: {
        instances: []
    },
    JS_Hoge_Hello: function () {
        window.alert("Hello, world!");
    },
    JS_Hoge_Create: function (url) {
        try {
            url = Pointer_stringify(url);
            var socket = new WebSocket(url);
            var instance = Hoge.instances.push(socket) - 1;
        } catch (e) {
            console.error(e);
            return -1;
        }
        return instance;
    },
    JS_Hoge_Release: function (instance) {
        Hoge.instances[instance] = null;
    }
};

autoAddDeps(LibraryHogeWebGL, '$Hoge');
mergeInto(LibraryManager.library, LibraryHogeWebGL);

参考URL

jslib に書いたコードの場所

Console で次のように書くと直接呼び出すことができます。
関数名の先頭にアンダーバーが追加されています。

unityInstance.Module.asmLibraryArg._JSxxxx()

Unity側、ブラウザ側の両方に関連すること

C#からJSにバイト配列を渡すテンプレート

*.cs
using System.Runtime.InteropServices;

public class Hoge {
    [DllImport("__Internal")]
    static extern void JS_Hoge_TestByteArray(byte[] data);

    public void TestByteArray() {
        // 配列を渡したつもりだけど、実際には HEAPxx 上のポインタが渡される。
        byte[] data = new byte[] { 1, 2, 3, 4 };
        JS_Hoge_TestByteArray(data);
    }
}
*.jslib
JS_Hoge_TestByteArray: function (dataPtr) {
    console.log(HEAPU8[dataPtr]);
    console.log(HEAPU8[dataPtr + 1]);
    console.log(HEAPU8[dataPtr + 2]);
    console.log(HEAPU8[dataPtr + 3]);
}

C#からJSに文字列を渡すテンプレート

*.cs
using System.Runtime.InteropServices;

public class Hoge {
    [DllImport("__Internal")]
    static extern void JS_Hoge_TestString(string str);

    public void TestString() {
        // 文字列を渡したつもりだけど、実際には HEAPxx 上のポインタが渡される。
        string str = "test";
        JS_Hoge_TestString(str);
    }
}
*.jslib
// Pointer_stringify() にポインタを渡すと文字列になる
JS_Hoge_TestString: function (strPtr) {
    var str = Pointer_stringify(strPtr);
    console.log(str);
}

JSからC#に文字列を返すテンプレート

*.jslib
var str = 'result';
var length = lengthBytesUTF8(str) + 1;
var buffer = _malloc(length);
stringToUTF8(str, buffer, length);
return buffer;

lengthBytesUTF8(), _malloc(), stringToUTF8() は Emscripten の関数です。
stringToUTF8() については、次のURLに説明があります。

lengthBytesUTF8(), _malloc() はドキュメントが見つけられませんでした。

JSからC#に配列を返すテンプレート

下のコードをそのまま書いて試したわけではないので、動かなかった、もしくは適切な書き方が分かるかたはコメント欄で教えて頂けると助かります。
(実際には WebSocket の onmessage コールバックで動作することを確認しました。)

*.cs
using AOT;
using System;
using System.Runtime.InteropServices;

public class Hoge {
    [DllImport("__Internal")]
    static extern void JS_Hoge_Callback(Action<IntPtr, int> callback);

    public void Test() {
        JS_Hoge_Callback(HogeCallback);
    }

    // static でないと呼び出せない
    [MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
    static void HogeCallback(IntPtr ptr, int length) {
        byte[] data = new byte[length];
        Marshal.Copy(ptr, data, 0, length);
        for (int i = 0; i < data.Length; i++) {
            UnityEngine.Debug.LogFormat("{0}: {1}", i, data[i]);
        }
    }
}
*.jslib
JS_Hoge_Callback: function (callback) {
    var data = new ArrayBuffer(8);
    var buffer = _malloc(data.byteLength);
    writeArrayToMemory(new Uint8Array(data), buffer);
    dynCall('vii', callback, [ buffer, data.byteLength ]);

    // 注: _free()を呼び出したほうがいいかも
}

byte[] ではなく int[] 等を返す場合は、 length 引数に data.byteLength を渡す代わりに TypedArray.prototype.length を渡す必要がありそうです。
(次のコードは実際に動かして確認したことがないので、動かなかった場合、もしくはより良い書き方、解決策をご存知のかたはコメントを頂けると助かります。)

// int[] が返せるであろうというコード (未確認)

// data のバイトサイズが 4 の倍数でないと Int32Array を作成する時にエラーが出る
// Uncaught RangeError: byte length of Int32Array should be a multiple of 4
var data = new ArrayBuffer(8);
var buffer = _malloc(data.byteLength);
var array = new Int32Array(data);

// アライメントが揃っているのか疑問
HEAP32.set(array, buffer / 4);
dynCall('vii', callback, [ buffer, array.length ]);

Marshal.Copy の説明は次の場所に書かれています。

ArrayBuffer の説明は次の場所に書かれています。

writeArrayToMemory() の説明は次の場所に書かれています。

C#でインスタンス管理をする時のテンプレート

JSから呼び出される、C#のコールバックメソッドはstaticである必要があるため、C#側でもインスタンス番号と実際のインスタンスを紐付けるリストを持つ必要が出てきます。JSからC#のコールバックメソッドを呼び出すタイミングで、JS内で管理されているインスタンス番号を一緒に渡すと、C#側でもインスタンスを特定することができます (すみません、自分でも読みにくい文になってしまいました)。

コールバックを扱う時は多くのケースで、このサンプルに近いものが必要になると思います。

*.cs
using AOT;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public class Hoge {
    [DllImport("__Internal")]
    static extern int JS_Hoge_Create();

    [DllImport("__Internal")]
    static extern void JS_Hoge_Release(int instance);

    [DllImport("__Internal")]
    static extern void JS_Hoge_RegisterCallback1(int instance, Action<int> callback);

    [DllImport("__Internal")]
    static extern void JS_Hoge_Call1(int instance);

    // インスタンス管理用
    static Dictionary<int, Hoge> _instances;

    // インスタンスの番号
    readonly int _instance;

    // static コンストラクタ
    // プログラム開始時に一度だけ実行される
    static Hoge() {
        _instances = new Dictionary<int, Hoge>();
    }

    // コンストラクタ
    public Hoge() {
        _instance = JS_Hoge_Create();
        if (_instance < 0) {
            UnityEngine.Debug.LogError("error 1: new Hoge()");
            return;
        }
        if (_instances.ContainsKey(_instance)) {
            UnityEngine.Debug.LogError("error 2: new Hoge()");
        }
        _instances.Add(_instance, this);

        // ここでコールバックメソッドを JS 側に登録しておく
        JS_Hoge_RegisterCallback1(_instance, Callback1);
    }

    // 後処理用
    public void Dispose() {
        JS_Hoge_Release(_instance);
        if (_instances.ContainsKey(_instance)) {
            _instances.Remove(_instance);
        }
    }

    // コールバックの呼び出しサンプル
    public void Call1() {
        JS_Hoge_Call1(_instance);
    }

    [MonoPInvokeCallback(typeof(Action<int>))]
    static void Callback1(int instance) {
        if (_instances.ContainsKey(instance) == false) {
            UnityEngine.Debug.LogError("error 1: Callback1()");
            return;
        }
        var hoge = _instances[instance];
        if (hoge == null) {
            UnityEngine.Debug.LogError("error 2: Callback1()");
            return;
        }
        UnityEngine.Debug.Log("Callback1() called");
    }
}
*.jslib
// 例: WebSocket を扱う JS
var LibraryHogeWebGL = {
    $Hoge: {
        instances: [],
        callbacks: {}
    },
    JS_Hoge_Create: function () {
        try {
            var socket = new WebSocket('ws://localhost');
            var instance = Hoge.instances.push(socket) - 1;

            // コールバックのリストを初期化
            Hoge.callbacks[instance] = {
                callback1: null
            };
        } catch (e) {
            console.error(e);
            return -1;
        }
        return instance;
    },
    JS_Hoge_Release: function (instance) {
        Hoge.instances[instance] = null;
        Hoge.callbacks[instance] = null;
    },
    JS_Hoge_RegisterCallback1: function (instance, callback) {
        // コールバックを登録する処理のサンプル
        var callbacks = Hoge.callbacks[instance];
        if (callbacks == null) {
            console.error('error: JS_Hoge_RegisterCallback1()');
            return;
        }
        callbacks.callback1 = callback;
    },
    JS_Hoge_Call1: function (instance) {
        // コールバックの呼び出しサンプル
        var callbacks = Hoge.callbacks[instance];
        if (callbacks == null) {
            console.error('error: JS_Hoge_Call1()');
            return;
        }
        dynCall('vi', callbacks.callback1, [ instance ]);
    }
};

autoAddDeps(LibraryHogeWebGL, '$Hoge');
mergeInto(LibraryManager.library, LibraryHogeWebGL);

このサンプルも改善の余地があると思いますので、改善方法をご存知のかたはコメント欄で教えて頂けると助かります。

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

SourceTreeのターミナルから作業ツリー内の変更を全破棄、未追跡を全削除、メモ

Unityをアップデートしたら差分が山ほど出た

全部を破棄しようとして作業ツリー内をたくさん選択すると
SourceTreeが固まるのでコマンドラインからクリーンする。
同じく未追跡のファイルを全部削除しようとして全選択してから削除しようとすると固まるので
コマンドラインからクリーンする。

こちらの記事を参考にしました。
https://awesome-linus.com/2019/05/13/git-clean-untracked-files-delete/

■ターミナルを開く
 SourceTreeを開く→メニューの右上のターミナルアイコンがあるのでクリック

■コマンドラインから破棄対象ファイルを全削除

git checkout .

■コマンドラインから未使用ファイルを全削除

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