【BeamNG drive】Mod作成記#2 — UI Mod応用編 —

BeamNG.drive[JP]

第2回となる今回は、UI Modの応用編をお送りいたします。

※基本編はこちら

「こういうことを実現したい場合どうする?」というのを逆引き形式でご紹介していきます。

JavaScriptでのJSONファイル読み込み/書き込み

JSONファイル読み込み

以下のように記載することでJavascriptでJSONファイルを読み込むことが可能です。

bngApi.engineLua('jsonReadFile(' + bngApi.serializeToLua('/ui/modules/apps/xxx/xxx.json') + ')', (settings) => {
  // 任意の処理
  console.log(Object.entries(settings));
});

【補足事項】
・ jsonReadFile()の引数はJSONファイルパスです。’/ui/modules/apps/xxx/xxx.json’ の部分は各自のパスで適宜置換してください。

・bngApiを使用するので、前回記事を参考にbngApiが使用できるようにしておいてください。

・戻り値(上の例だとsettings)には読み込んだJSONファイルの中身が格納されます。
例えば、JSONファイルが以下の場合、

{
  "a":{
    "aa":"hoge"
  },
  "b":{
    "bb":"fuga"
  }
}

settingsには {a: {aa: 'hoge'}, b: {bb: 'fuga'}} のような連想配列が格納されます。

JSONファイル書き込み

逆に書き込みを行う場合は以下のように記載します。

bngApi.engineLua('jsonWriteFile(' + bngApi.serializeToLua('/ui/modules/apps/xxx/xxx.json') + ', ' + bngApi.serializeToLua({a: {aa: 'hoge'}, b: {bb: 'fuga'}}) + ', ' + bngApi.serializeToLua(true) + ')');

【補足事項】
・ jsonWriteFile()の引数は、JSONファイルパス、書き込む値、整形要否です。
 ・JSONファイルパスは、読み込みと同じく各自のパスを指定してください。
 ・書き込む値は、JSONの形式に適合した連想配列を指定してください。
 ・整形要否は、書き込み時に改行・インデントを入れて整形するかどうかです。true/falseを適宜指定してください。(未指定の場合はfalseで動作します)

・’/ui/modules/apps/xxx/xxx.json’ の部分はJSONファイルのパスになりますので適宜置換してください。

・bngApiを使用するので、前回記事を参考にbngApiが使用できるようにしておいてください。

・v0.23より、UIフォルダ内のファイルを変更すると画面の更新が走るようになりました。書き込みを行ったタイミングで画面が更新されるので注意してください。

SVG画像を動的に変化

// elementは構造に合わせて適宜指定する。
// 以下はtemplateにSVGのオブジェクトだけがある場合の例。
element.on('load', function () {
  var svg = element[0].contentDocument;

  // onclickやstreamUpdate等、トリガーとしたい操作のスコープ内に書く
  scope.$on('streamsUpdate', function (event, data) {
    // 例)id="obj1"の要素の幅を10に設定する
    svg.getElementById('obj1').setAttribute('width', 10);
 
  });
});

【補足事項】
・特になし

SVG画像の文字を動的に変化

// elementは構造に合わせて適宜指定する。
// 以下はtemplateにSVGのオブジェクトだけがある場合の例。
element.on('load', function () {
  var svg = element[0].contentDocument;

  // onclickやstreamUpdate等、トリガーとしたい操作のスコープ内に書く
  scope.$on('streamsUpdate', function (event, data) {
    // 例)id="text1"の文字列に'Hello World!'を設定する
    svg.getElementById('text1').textContent = 'Hello World!';

  });
});

【補足事項】
textContentの部分はinnerText / innerHTMLでも可だそうです。
それぞれの違いについては→参考

SVG画像でユーザ操作(onclick等)

まずは、SVGのオブジェクトがユーザ操作を受け付けるように、SVGにpointer-events:autoを設定します。

<object style="pointer-events:auto; cursor:pointer;" type="image/svg+xml" data="/path/to/my/svg/image.svg"></object>

cursor:pointer はユーザがマウスをホバーした際にカーソルの形がポインターになる指定です。

次に、SVGがクリックされた時のリスナーをJavascriptに記載します。

// elementは構造に合わせて適宜指定する。
// 以下はtemplateにSVGのオブジェクトだけがある場合の例。
element.on('load', function () {
  var svg = element[0].contentDocument;

  // ここで指定するidはSVG内のid
  var btn1 = angular.element(svg.getElementById('btn1'));

  btn1.on('mousedown', function () {
    // ここにクリック時に行いたい処理を書く
  });
});

これで、ユーザがbtn1が定義されたSVGをクリックすると、定義した処理が発火するようになります。

【補足事項】
pointer-events:auto を指定したオブジェクトに文字等があると、ユーザの文字選択も有効になってしまい、割と不便です。ユーザ操作を受け付けるオブジェクトは分けておく方が良いかと思います。

ログの埋め込み

以下のようにconsole.log()にログ出力したい内容を記載することでログを埋め込むことが可能です。
※app.jsに書く

var x = 'Hello World!';
console.log(x);


以下のような出力になります。
※下から3行目の緑色の行

【補足事項】
・リアルタイム更新(‘streamsUpdate’)の箇所にログを埋め込むとログが大量に出て動作自体が重くなる場合があります。
ユーザ操作契機でのログ出力やif文内でのログ出力になるよう工夫しましょう。

・連想配列などは出力が”[object Object]”のような形になってしまい、そのままではログが意味を成しません。

Object.entries()等を使用して配列に展開して出力するか(下の例を参照)、要素を指定して出力してあげましょう。

// Object.entries()の使用例
var x = {a: 'apple', b: 'pen'};
console.log(Object.entries(x));

bngApi(Lua)を確かめる時のTIPS

bngApiを確かめる際にSystem Consoleの”GE – Lua”で対象の関数を実行すると思うのですが、実行するだけだと、実行結果がコンソールに表示されません。

以下のように明示的にprint()でコンソールに出力する必要があります。

x = extensions.gameplay_traffic.getPursuitData()
print(x)


また、戻り値がtable型の場合は以下のように記載することでtableの中身を出力できるので便利です。

// table型を返すbngApi
x = extensions.gameplay_traffic.getPursuitData()

// xに格納されたtable型のキー・バリューをすべてコンソールに出力する
for k, v in pairs(x) do print(k, v) end

// キー名が分かっている場合は直接指定してもよい
print(x['key'])

【補足事項】
・特になし。

HTML上にtable(表)を動的に生成

まずはHTMLの方に以下のような枠だけ用意しておきます。

<table>
  <thead>
    <tr>
      <th>{{ Header1 }}</th>
      <th>{{ Header2 }}</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in table1">
      <td>{{ row.col1 }}</td>
      <td>{{ row.col2 }}</td>
    </tr>
  </tbody>
</table>


次にJavascriptの方に以下のように記載します。

angular.module('hoge.apps')
.directive('fuga', [function () {
  return {
    template: '~省略~',
    replace: true,
    restrict: 'EA',
    // Controller定義を書く
    controller: ['$scope', function($scope){
      // HTMLにテーブルを表示するための元となる配列を定義する。
      // 配列名はHTMLの名前と合わせておくこと。
      $scope.table1 = [];
    }],

    link: function (scope, element, attrs) {

    /* ~中略~ */

      addBtn.on('mousedown', function () {
        // 行を追加する場合は配列に連想配列を追加する。
        // 連想配列のキーはHTML側に合わせておくこと。
        scope.table1.push({col1: "hoge", col2: "fuga"});
        scope.table1.push({col1: "hoge", col2: "fuga"});

      }

      deleteBtn.on('mousedown', function () {
        // テーブルを消す場合は配列の長さを0にする
        scope.table1.length = 0;
      }

    /* ~以下略~ */

【補足事項】
・上記はマウスクリックで行を追加したりテーブルを消したりしたい場合の例です。かなり端折っているので適宜脳内補完してください。

・テーブルを削除する場合、あくまで文字を消すことで見えないようにしているだけなので、背景や罫線を付けるとうっすらと表示されてとてもダサいので注意が必要です。

参考

公式ドキュメント
https://documentation.beamng.com/modding/

表の動的作成
https://www.buildinsider.net/web/angularjstips/0033

コメント / COMMENT

タイトルとURLをコピーしました