OMSI WIki - Scripting System(英語)から要訳 & 補足
多少のプログラミングの知識が無いと難しいです。基本演算やif文、その他基本用語レベルまでは抑えておくと読みやすくなります。
概要
スクリプト内ではOMSIのシステム変数やファンクションにアクセスできる他、音も鳴らせます。
シーナリーオブジェクトや車両にスクリプトを追加することで、ゲーム内の時刻表と連動する発車標や、独自のドア、トランスミッションなどを作ることが出来ます。
スクリプトのファイル構成
- 実行可能なコードを記述したスクリプトファイル(*.osc)
- スクリプト内で使用する独自の変数を宣言したvarlistファイル・stringvarlistファイル
- OMSI/Programディレクトリ内……OMSI全体で使用する変数の定義用。OMSI本体のシステムファイル等向け
- その他……各シーナリーオブジェクト・車両内で使用するユーザー定義用
- スクリプト内で使用する定数や性能曲線を定義したconstfileファイル
これらを合わせて一つのスクリプトになります。
各シーナリーオブジェクト・車両で使用するスクリプトはコンフィグファイル(*.bus;*.scoなど)の[script]セクション、[varnamelist]セクション、[stringvarnamelist]セクション、[constfile]セクションで設定されています。
スクリプトファイルの基本的な記述方法
大文字・小文字は区別される
OMSIのスクリプトはケースセンシティブです。変数名等の大文字・小文字は区別されます。
※"S.L."や"L.L."については後述します。簡単に言うと、"S.L."が代入、"L.L."が取得です コード例: 'hensuに1を代入 1 (S.L.hensu) 'Hensuに2を代入 2 (S.L.Hensu) 'hensuを取得 (L.L.hensu) '1が取得され(、スタック0は1になり)ます
計算式
計算式は逆ポーランド記法によって記述されます。逆ポーランド記法の概要や内部処理についてはWikipediaが分かりやすく解説してくれているので、読んでみて下さい。
ここで理解しておきたいのが、逆ポーランド記法の「スタック」という概念です。
スタックとは、計算の途中経過を一時的に保存しておくためのものです。パソコンに精通している方はメモリのことだと思って下さい。
スタックは複数個あり、計算式を1文字ずつ解読しながら、ある決まりに従って各スタックに数字や文字列を記憶させていきます。
文章で説明すると分かりづらいと思うので、ここでいくつかの例を見ていきましょう。
例1: 1 + 4
これを逆ポーランド記法で記述すると、
1 4 +
となります。この計算式を1文字ずつ解読していきます。
ステップ | 解読する文字 | スタック0 | スタック1 | スタック2 | スタック3 | … |
初期状態 | 0 | 0 | 0 | 0 | … | |
1文字目 | "1" | 1 | 0 | 0 | 0 | … |
2文字目 | "4" | 4 | 1 | 0 | 0 | … |
3文字目 | "+" | 5 | 0 | 0 | 0 | … |
例2: (1 + 2) * (4 + 5)
これを逆ポーランド記法で記述すると、
1 2 + 4 5 + *
となります。この計算式を1文字ずつ解読していきます。
ステップ | 解読する文字 | スタック0 | スタック1 | スタック2 | スタック3 | … |
初期状態 | 0 | 0 | 0 | 0 | … | |
1文字目 | "1" | 1 | 0 | 0 | 0 | … |
2文字目 | "2" | 2 | 1 | 0 | 0 | … |
3文字目 | "+" | 3 | 0 | 0 | 0 | … |
4文字目 | "4" | 4 | 3 | 0 | 0 | … |
5文字目 | "5" | 5 | 4 | 3 | 0 | … |
6文字目 | "+" | 9 | 3 | 0 | 0 | … |
7文字目 | "*" | 27 | 0 | 0 | 0 | … |
なかなか難しい概念ではありますが、この2例でなんとなく分かったのではないでしょうか。
スタック関連で最も大事なのは、スタック0にはその時点での計算結果が記憶されているということです。
また、スタックの記憶は計算式を計算した後も消去されることはなく、次に計算して上書きされるか、スクリプトブロックから出るまでは残り続けます。
この仕様のお陰で、以下のような書き方が出来るわけです。
コード例: 'if文…hensuが1ならtrue (L.L.hensu) 1 = {if} '単に数字のみを書くとスタック0に代入される 10 {else} 20 {endif} 'hogeにスタック0の数値を代入 l0 (S.L.hoge)
型
計算式中の数値はfloat型(32bit単精度浮動小数点実数)、文字列はstring型として認識されるとのことです。
特に書かれてはいませんが、OMSI2がDirectXのエラーで強制終了されることが多いことから、恐らくC++のデータ型に準拠しているものとみて良いと思います。
特殊キーワード
コメント
「'」(シングルクォーテーション)から始まる行はコメントとなります。ただし、「'」の前に空白などの文字があった場合はコメントとして認識されません。
コード例: 'この行はコメントです 'シングルクォーテーションの前に空白があるので、この行はコメントではありません
スクリプトブロックの開始行・終了行
キーワード | 説明 |
{frame} | OMSIの描画の毎フレームごとに実行されます。 |
{init} | OMSIの初期化時(起動時)に実行されます。 |
{frame_ai} | この車両がユーザーにフォーカスされていないとき(AI車等)に限って、OMSIの描画の毎フレームごとに実行されます。シーナリーオブジェクトのスクリプト内に記述しても機能しません。 |
{macro:名前} | 指定した名前のマクロが呼び出されたときに実行されます。ここで定義したマクロは、別のスクリプトファイルからも呼び出すことが出来ます。 |
{trigger:名前} | 指定した名前のトリガーがOMSI本体から呼び出されたときに実行されます。 |
{end} | スクリプトのブロックの終了部分を示します。 |
マクロの呼び出し
(M.L.マクロ名) と書くとマクロを呼び出せます。
マクロの呼び出しは、必ずそのマクロが定義されるより前に書く必要があります。
コード例: {init} 'my_macroを実行 (M.L.my_macro) (L.L.hensu) '10が取得される {end} {macro:my_macro} 'hensuに10を代入 10 (S.L.hensu) {end}
推奨されるスクリプトファイルのファイル構成
全てのスクリプトを1つのファイルにおさめるのではなく、複数個のスクリプトファイルに分割することが望ましいとされています。
以下、公式Wikiにて推奨されている「メインファイル」、「サブシステムファイル」に分割する方法についての説明です。いわゆるオブジェクト指向の考え方と似たものがあります。
メインファイル
{frame}、{init}から始まるスクリプトブロックのみが記述されており、各ブロックの内容は全てマクロによってサブシステムファイルに分割するべきです。
サブシステムファイル
サブシステムファイル毎に*.scoファイル(訳者注:シーナリーオブジェクトに限った話?)、及び必要に応じてvarlist・stringvarlistファイル、constファイルを作ります。
サブシステムファイルのメインのトリガーの名前は{trigger:サブシステム名_init}、{trigger:サブシステム名_frame}とすると良いでしょう。
トリガーについての補足
キーやマウスの入力時に呼び出されるトリガーがあります。
※英語版Wikiの説明が不足気味なため、本項のみドイツ語版Wikiの情報を参考にしました。ドイツ語については機械翻訳を使用しているので、訳し間違い等あれば修正してもらえるとありがたいです。
キー入力時
キーが押された時に{trigger:キーコンビ名}、離された時に{trigger:キーコンビ名_off}が呼び出されます。
マウス入力時
マウスボタンが押された時に{trigger:マウスイベント名}、ドラッグされた時に{trigger:マウスイベント名_drag}、離された時に{trigger:マウスイベント名_off}が呼び出されます。
演算子・システム変数・システムコマンド
スタック関連
キーワード | 説明 |
%stackdump% | (デバッグ用)現在のスタックの情報をダイアログとして表示します。OMSI 2では若干動作が不安定な模様。 |
s0、s1、…、s7 | 指定したIDのスタックに数値を代入します。 |
l0、l1、…、l7 | 指定したIDのスタックに記憶されている数値を取得します。 |
d | 全スタックの数値を1スタックずつ後にずらした上で、スタック1の値をスタック0に複製します。 |
$msg | ログファイル(Steam/SteamApps/common/OMSI 2/logfile.txt)に文字列スタック0に記憶されている文字列を書き込みます。 |
$d | dコマンドの文字列版です。 |
コード例: 'ログファイルにログを書き込む "Message Test" $msg '(1 + 2) * (4 + 5)を計算する 1 2 + 4 5 + * 'スタック0には27が記憶される 'hensuにスタック0の数値を代入する l0 (S.L.hensu) 'hensuには27が代入される 'スタック1にhensuを2倍した数値を代入する (L.L.hensu) 2 * s1 'スタック1には54が代入される 'スタックの状況をダイアログ表示 %stackdump%
論理演算子
キーワード | 説明 |
&& | 論理積「かつ」 |
|| | 論理和「または」 |
! | 非等値「ではない」 |
0をFalse(偽)、0以外の全ての数値がTrue(真)と認識されます。
コード例: 'hoge=0かつfuga≠10であれば1(True)、そうでなければ0(False)をhensuに代入 (L.L.hoge) 0 = (L.L.fuga) 10 = ! && (S.L.hensu)
比較演算子
本項では、説明を簡単にするため「f(x)=文字列スタックxに記憶されている文字列をASCIIコードで表現したときの数値」とします。
例えば、文字列「A」はAが41なので41、「aAB」はaが61、Aが41、Bが42なので614142、同様にして「OMSI」は4F4D5349となります。
キーワード | 説明 |
= | スタック1=スタック0 |
< | スタック1<スタック0 |
> | スタック1>スタック0 |
<= | スタック1≦スタック0 |
>= | スタック1≧スタック0 |
$= | f(1)=f(0) |
$< | f(1)<f(0) |
$> | f(1)>f(0) |
$<= | f(1)≦f(0) |
$>= | f(1)≧f(0) |
コード例: '0≦hoge<10であれば1(True)、そうでなければ0(False)をhensuに代入 0 (L.L.hoge) <= (L.L.hoge) 10 < && (S.L.hensu) 'mojiretsuに文字列"A"を代入 "A" (S.L.mojiretsu) $= 'mojiretsuに代入されている文字列が"B"よりアルファベット順で前なら1(True)、そうでなければ0(False)をhensuに代入 (L.L.mojiretsu) "B" < (S.L.hensu) 'AはBより前なので1(True)が代入される
数学関連
キーワード | 説明 |
+ | スタック1+スタック0 |
- | スタック1-スタック0 |
* | スタック1×スタック0 |
/ | スタック1÷スタック0 |
% | スタック1%スタック0(スタック1をスタック0で割った余り) |
/-/ | 正負変換。-(スタック0) |
sin | 正弦。sin(スタック0) |
arcsin | 逆正弦。asin(スタック0)⇔スタック0=sin kのとき、k ※OMSI2のみ |
arctan | 逆正接。atan(スタック0) ※OMSI2のみ |
min | スタック1とスタック0のうち小さい方 |
max | スタック1とスタック0のうち大きい方 |
exp | 指数関数。ネイピア数e^(スタック0) |
sqrt | 平方根。√(スタック0) |
sqr | 2乗。(スタック0)^2 |
sgn | スタック0の数値の符号。スタック0<0のとき-1、スタック0=0のとき0、0<スタック0のとき1 |
pi | 円周率 |
random | 0以上(スタック0の数値)未満のランダムな整数 |
abs | 絶対値。|スタック0| |
trunc | 整数に切り捨て。スタック0=a+b(aは整数、0≦b<1)のとき、a |
コード例: '中心(0,0)、半径1の円の円周上を1°/ミリ秒の速度で動く点のX座標をpoint_x、Y座標をpoint_yに代入するサンプル。 'テストはしていないので動かないかもしれません。もし動かなかった場合はそっと修正して頂けるとありがたいです。 {init} 'point_degree=0(0°)で初期化 0 (S.L.point_degree) {end} {frame} 'point_degreeに現在の角度を代入(0°~359°の範囲で表現) (L.L.point_degree) (L.S.TimeGap) + 360 % (S.L.point_degree) '現在の角度をラジアンに変換 (L.L.point_degree) 360 / 2 * pi * 'Y座標をpoint_yに代入 sin (S.L.point_y) '|cos|を計算(1-sin^2=cos^2を利用) 1 l1 sqr - sqrt '|cos|と現在の角度からcosを計算(90°以上270°以下の場合cos=-|cos|、それ以外の場合cos=|cos|) 90 (L.L.point_degree) <= (L.L.point_degree) 270 <= && {if} l1 -1 * {else} l1 {endif} 'X座標をpoint_xに代入 (S.L.point_x) {end}
文字列操作関連
執筆者は力尽きました。どなたかお願いします…
キーワード | 説明 |
変数への代入・取得
スクリプト毎に宣言した変数の他、OMSI本体からの情報もシステム変数を通じて取得出来ます。
キーワード | 説明 |
(L.S.変数名) | システム変数の数値を取得 |
(L.L.変数名) | 変数の数値を取得 |
(L.$.変数名) | 文字列変数の文字列を取得 |
(S.L.変数名) | 変数にスタック0の数値を代入 |
(S.$.変数名) | 文字列変数にスタック0の文字列を代入 |
加筆訂正随時募集
参考になるリンク
Scripting System - OMSI Wiki (英語)
FAQ - OMSI Wiki (英語)
オブジェクトのアニメーション(英語・中国語)
コメント
- スクリプトの解説ありがとうございます!そうかこれ知っておかないと自分でMOD作ることもままならないですよね。ところで『型』の項目で本体がアンリアルエンジン4で作成してあるとありますが、UE4を使ってるのはアドオンのBus Company Simulatorのマルチ画面で、本体は別かなぁと。 -- 2020-10-04 (日) 04:53:15
- 本当ですね。嘘を書いてしまい申し訳ないです。 -- 執筆者? 2020-11-09 (月) 19:40:59
- 確か本体はDirectX9でしたっけ? ひとまず該当箇所は消しておきます。 -- 執筆者? 2020-11-09 (月) 19:42:31