📝

Unityへの組み込み言語として、Miniscriptの紹介。

2021/05/13に公開

0.はじめに

2022/6/8 記述を大きく調整(大筋に変更なし)
2023/4/23 リンクを追加
2023/7/19 タグ追加

はじめまして、MachiaWorksと申します。
仕事の傍ら、趣味でゲーム・曲・グラフィック・シンセサイザーの製作等
とりあえず興味のあるものに対して色々と手を出しています。

今回は、Unityへスクリプト言語を組み込む際の選択肢として、
「Miniscript」という言語を紹介します。

1. Unityにスクリプトを組み込むシチュエーション・メリット

実際に導入するシチュエーションの提示
Unityでスクリプトを導入したいというケースは割とあると考えてます。

例えば、バランス調整を行うのにスクリプトを使わない形だと、
Editor上のScriptableObjectを調整したり、
C#のコードを書き直したりする形になるかと思います。
実際、Unityにおいては上記の調整が基本的な手段になるものと考えます。

ただ、Editorを操作してEditor自体が強制終了した場合、ScriptableObjectの数値を入力しなおす必要があります。
また、ScriptableObjectの項目がだんだんと多くなってきて、Editorの動作自体が
怪しくなってきて・・・というケースが割とあるので、理想としてはScriptableObjectの調整も外部で行う等、可能ならEditor上の数値やオブジェクトの位置を入力するのは最小限にしたいところです。

また、C#で記述のうえ修正していく場合、修正のたびにビルドや全体の再構成が発生します。
ファイル数が多くなるたびに再構成に時間がかかるので、修正してすぐ動かす、ということも難しいです。

上記のケースでインタプリタ形式のスクリプトを導入すると、上記の問題を解決できるかと思います。

まず思いつくのが、敵のバランス調整のためにスクリプトを書くパターン。
敵の出現タイミングや弾を吐くタイミング等、細かいバランスを
調整するときに割と頻繁に書き換えを行います。

テキストとしてアセットの数が増えることはあるでしょうけど、書き換えが行われてもそれほどの時間はかかりません。
アセットの参照先に変更はなく内容だけの変更が可能なので、C#の再構成が発生しない形になります。
(テクスチャを指定形式に変換する等もなく、単にテキストを読み込んでくる形になる)

以下、簡単にスクリプト導入のメリデメをまとめました。

スクリプト導入時のメリット(前述)

  • 修正のたびにUnity上でビルド・再構成を行わなくて済む(アセット扱い)
  • (ScriptableObject弄るときと比べて)別ツールで修正するので、UnityEditorが強制終了したときも復帰しやすい
  • 調整・メンテナンスをするときに、ロジックを直接触らずに済む

スクリプト導入時のデメリット

  • スクリプトによるコントロールの仕組みを作るのに時間がかかる
  • 新たに言語を覚える必要がある
  • 処理速度についてC#より遅い傾向がある

2. Miniscriptの紹介・特徴

場所・Github

Webページ:
https://miniscript.org/
Github:
https://github.com/JoeStrout/miniscript

Miniscriptの特徴

  • 言語のシンプルさ
    これは新たに覚える言語がすぐ使えるか、という部分ですが、
    言語の文法がシンプルなので、割とすぐに使えるかと思います。
    下記リンクに文法がまとまってますが、1ページにすべて収まっております。
    超コンパクトです。

参考:
https://miniscript.org/files/MiniScript-QuickRef.pdf

  • 組み込みの楽さ(C#想定)
    元々Unityへの組み込みを想定されて作られたおかげか、
    Unityへの組み込みについては簡単に行えます。
    また他環境への組み込みもそれほど難しくないものと考えます。
    下記にも方法の案内が記載されてますが、基本的にはマニュアルに沿った形で出来てしまうので便利です。
  • 半分JIT形式
    一度コンパイルしてバイトコードを保管、バイトコードを読み込んで
    処理する形式なので実装者がstring型を毎回使う等がありません。
    半分と書いたのは、あくまでコンパイルはC#側で明示的に行って、以後実行のためのバイトコードは利用インスタンス内に保管されるからです。
    よって、各オブジェクトの読み込み時に一気にソースコードをコンパイルし、あとはバイトコードを実行し続けるという形であれば、ヒープやCPU使用率の削減にもつながるかと思います。
  • おまけ:C#で書かれてる
    C#が動く環境であれば動くかと思うので、他環境に移植する際も
    ソースコードを読み込ませてエラーがでなければ問題ないです。
    (いざとなったら自分で修正できますし。あとMITライセンスなので利用について制約も少ないかなと)ちなみにUnityの組み込みを最初に想定していると記載しましたが、実際のところ他の環境でも動くので、他プログラムへの組み込みも想定してOKになってます。

実際、公式でMiniscript組み込んだコマンドラインツールを公開しております。
(C++で記載されており、これを組み込んだ形)
 
ただ、C#で書かれたMiniscriptを利用する場合、言語としてGCが発生するのは避けられない状態になっております。
実際通常のテキスト処理や組み込み関数を利用する際GCが発生することを確認済みです。
(バイナリの再実行でも発生する)
 
対応として、記述方法を工夫することでGCの発生を抑えることは可能ですし、Unity上でインクリメンタルGCを利用すれば影響を最小限に抑えることが可能です。
実際IL2CPPを利用してビルドしたスタンドアロンのアプリにおいて、1秒で60回複数のスクリプトを大量に読み込むという処理を行なっていました。
1秒あたり5MB程度ヒープが増加してましたがすべてインクリメンタルGCで開放されており、処理速度にも影響は出ませんでした。
もしくはC++版Miniscriptをプラグイン形式で呼び出して実行すれば問題ないかと思います。

デメリットへの対応

上で書いた文章を引用します。

スクリプト導入時のデメリット

  • スクリプトによるコントロールの仕組みを作るのに時間がかかる
  • 新たに言語を覚える必要がある
  • 処理速度についてC#より遅い傾向がある

1項目ずつデメリットへの対応状況を記載していきますね。

・スクリプトによるコントロールの仕組み作るのに時間がかかる

前述の通り、「アプリケーションへの組み込みが楽」なので、
少なくとも1本スクリプトを処理する機能を実装するには、
それほど時間がかからないものと考えます。
(元々Unityでの利用を想定しているらしく、公式で組み込みガイドなんてものもあるので)
ちなみにyield機能もあるので、テキストADVみたいな読み込みも可能です。

・新たに言語を覚える必要がある

前述の通り、文法はすごいシンプルにまとめられていて、
まず他の言語を使ったことがある人であれば問題なく使えます。
内容としてはCとかBasicあたりの言語をすごいシンプルに再構築した感じかなと。

・処理速度についてC#より遅い傾向がある

正直、これはC#上で言語を処理する関係上仕方ないものと考えます。
ただ、前述の通り半分JIT形式のため、バイトコードを処理する
形になるため、性能劣化があるにせよ少しばかり抑えられてると考えます。

3. 実際の使い方

基本的には、公式ページの「Unity Integration Guide」をお読みいただくほうが
簡単に、かつ要点を抑えてまとめられてます。
まずはここのコードを見るのがいいかと思います。
あとは、AssetStoreにあるアセットに組み込みのサンプルがアップされてるので、
それで見てしまうのもアリかと。(有料ですが)

https://miniscript.org/files/MiniScript-Integration-Guide.pdf

利用シチュエーション

単体で利用する場合

Unity Integration Guideの「1. Overview」に記載があります。
これだけで出来てしまうのは楽です。計算結果についてもコンソールへの出力が可能なので、
単に計算させるだけであればあっという間にできます。
また、エラー取得についてはtry~catchを仕込んでおけば問題ないです。

オブジェクトと協調する場合

Unity上のオブジェクトと協調する(変数更新・行動分岐等)Unity Integration Guideの
「2. Bridging the Divide」に、Unityと協調させる内容が記載されてます。
これを使えば基本的には問題ないです。

例えば特定の方向にキャラクターを動かす等だと

  • グローバル変数をもたせる
  • 組み込み関数を使う(後述)

という方法があります。
ただ、Miniscript上で指定したグローバル変数をUnityのUpdate関数等で別途アクセスする場合、どうもMiniscript上で計算されない模様。

自分はゲームへの組み込みで大量の同じクラスのオブジェクトが出てくるので、
グローバル変数を使うことは困難かなーと考え、
後者の「組み込み関数を使う」ことにしてます。

組み込み関数で特定命令を実行するときに、delegate命令で
特定クラスの内部処理を行ってもらい、変数等の更新を行うことにしました。
これで違和感なくオブジェクトの値更新も可能になっております。

組み込み関数の利用

Unity Integration Guideの「2. Bridging the Divide」に記載あり。

組み込み関数は、「Intrinsic」という単語で記載されております。
実装にはdelegateを利用します。
これによって、利用するクラスごとに必要な組み込み関数を定義、ということも
可能になっております。

ただ、関数登録についてはグローバル変数で登録してあるみたいなので、
登録自体は1回行えば問題ありません。
自分の場合だと、オブジェクトに対し初期化を行う際、グローバル変数で
登録処理を行ったことを確認するためのフラグを持っておいて、
1回だけ起動できるように仕込んでおきました。

4.終わりに

簡素にまとめましたが、
まず、Unity上でスクリプトを実行するという選択肢は、
バランス調整という観点から、候補にあげてもいいのではないかと思います。

そこで言語を自作する等もアリと思いますが、このMiniscriptを選ぶという
選択肢は組み込み方法・言語の簡易さから使いこなす負荷が軽減されているため
自作アプリ・ゲームにとっても有効な選択肢と考えます。

みなさんもスクリプトを組み込んでみて、
ゲームのバランス調整に集中できる環境を作ってみてはいかがでしょうか。

ちなみに情報を知りたい場合公式ページを眺めるのが一番確実ですが、こちらのページに文法や機能の紹介等を随時まとめてます。
よかったら見てみて下さい。

http://machiaworx.s351.xrea.com/wiki/miniscrwiki/doku.php?id=start

Discussion