はじめてのミッションエディット スクリプトファイル編 -ArmA訓練所-

ArmA訓練所
2007/10/17

目次


スクリプトファイルを使ってみよう

前節ではトリガーを使ったスクリプトコマンドを実行する方法を説明しました。
トリガーでは単純なコマンドの実行から複雑なスクリプト処理を書くことも出来ますが、
実際に複雑な処理を書くのは困難でしょう。
そこでスクリプト処理をテキストファイルに記述し、それを実行させる方法が有効になってきます。
テキストファイルにスクリプトコマンドを記述したものをスクリプトファイルといいます。

スクリプトファイルのメリット

トリガーでもスクリプトコマンドを実行できるのに、なぜスクリプトファイルが必要なのでしょう。
それはスクリプトファイルにはトリガーよりも便利な機能がいくつもあるからです。

一番のメリットは引数(パラメータ)です。
処理に必要な情報の一部を引数にすることで処理の再利用が可能になります。
これは同じ処理を何度も書く必要性を減らし、開発効率を高めます。

二番目のメリットはローカル変数です。
ローカル変数はそのスクリプトファイル内のみで有効な変数で、他の処理で使っている変数名を気にする必要はありません。
そのため変数名に困らず長い処理を書くことが出来ます。

三番目のメリットは制御文です。
変数の値が特定の条件で処理を分岐させたり、処理を複数回ループ処理することが可能です。
こうした柔軟な処理を書きやすいことでより多様な処理を容易に実現できます。

四番目のメリットはスクリプトハンドルです。
スクリプトハンドルを使うと、そのスクリプトの実行が完了したかどうかをチェックしたり、
常にループする処理を外部から強制的に止めることが出来ます。(ArmAのSQF形式のみ、OFP不可)

スクリプトファイルの実行

スクリプトファイルの実行にはトリガーを含め、いくつかの方法がありますが、
基本的にはスクリプトコマンドが実行できる箇所でスクリプトファイルを実行できます。
ユニットのInitialization欄やトリガーなど、そしてスクリプトファイルからさらに別のスクリプトファイルを実行できます。
スクリプトファイルに記述した処理を実行させるには次のようなコマンドを使います。
SQS形式の場合
「[param] exec "scriptfilename";」
SQF形式の場合
handle = [param] execVM "scriptfilename";」
SQS形式とSQF形式はスクリプトファイルの書き方の違いですが、ArmAではSQF形式が推奨されています

スクリプトファイルの中身とはどういうものなのか

スクリプトファイルは拡張子が(.sqsや.sqf)である以外は普通のテキストファイルです。
ただし、OFPやArmAは日本語を処理できないため、使える文字は基本的に半角英数のみに限られます。
あとはトリガーに書いていたようなスクリプトコマンドを書いていくだけです。
単純なものから見ていきましょう。

hint "Script is running!";

このスクリプトを実行させると画面左上に「Script is running!」というメッセージが表示されます。
SQF形式で注意が必要なのは、命令文の最後にセミコロン( ; )を付けないといけないということです。
SQF形式はセミコロンで命令文の終わりを判断するため、これを付け忘れるとエラーとなります。
とはいえ、セミコロンを付けることだけを覚えればトリガーで書いたものと違いがないことが分かると思います。

変数を使う

トリガーでも変数を使うことは可能です。
しかしローカル変数を扱うことは出来ませんでした。
スクリプトファイルを使うとローカル変数を使うことが出来、開発効率が上がります。

_unitname = "shinmai";
_age = 24;

hint format [ "%1 is %2 years old", _unitname, _age ];

ここで使われている_unitnameや_ageがローカル変数です。
この例ではさほどローカル変数のメリットはありませんが、書き方のイメージをつかんでおきましょう。
このスクリプトを実行させると画面左上に「shinmai is 24 years old」と表示されます。

引数を使う

別項でも説明しましたが、スクリプトファイルは引数を受け取ることが出来ます。
例えば「samplehandle = [ shinmai, 24 ] execVM "displayAge.sqf";」という形で年齢を表示するスクリプトを実行した例を見てみましょう。

displayAge.sqf

_unitname = _this select 0;
_age = _this select 1;

hint format [ "%1 is %2 years old", _unitname, _age ];

上のスクリプトに_thisとかselectという単語が出てきました。
_thisはスクリプトファイルに与えられた引数を持つ配列として扱われます。
先頭にアンダースコアが付いているのでローカル変数となります。
つまり、このスクリプトに出てくる_thisは、他のスクリプトで使われる_thisとは別物であることを知っておきましょう。
selectは配列の1要素を取り出すコマンドです。そして配列は先頭を0番目として数えることに注意してください。
今回の_thisの中身は「_this = [ "shinmai", 24 ];」として定義したものと全く同じなので、
「_this select 0」は"shinmai"となり、「_this select 1」は24となります。
結果として先ほどの変数の説明と同じ動作をすることになります。

ここで「引数を使っても使わなくても同じなら引数を使わない方が楽でいいじゃないか」という考えを抱く方がいると思います。
確かにその考えは間違っていません。
しかし、引数を使うことのメリットを知れば、その考えはきっと変わると思います。
先ほどはshinmaiという人が24歳であることを表示していました。
では今度はbeteranという人が35歳であるという表示を出すことを考えてみましょう。

例1 引数を使わないパターン

_unitname = "beteran";
_age = 35;

hint format [ "%1 is %2 years old", _unitname, _age ];

例2 引数を使うパターン

samplehandle = [ beteran, 35 ] execVM "displayAge.sqf";

例1を見てみるとスクリプトの変数の値を2カ所書き換えて終わりです。
一見簡単なように見えます。
今回は単純なサンプルなので3行で済むスクリプトを用意していますが、これが50行、100行と複雑な処理をしていた場合に、
これらのソースをコピペで使い回すことになります。
そしてそのソースにバグが見つかったり、表示の最後にピリオドを入れようという変更をするとき、
それぞれのスクリプトファイルに変更を加えなければなりません。
これでは大きく開発効率が下がってしまいます。

例2を見てみましょう。
スクリプトファイルを呼び出して、引数のみを変更しています。
これはスクリプトがどんなに複雑でもこの1行で済むことを表しています。
バグの修正や表示の変更も一つのファイルを修正するだけで、shinmaiを表示してもbeteranを表示しても変更が反映されます。
同じ処理を使い回す場合に、複雑な処理であればなおのこと引数を使う方が便利です。

スクリプトハンドルを使う

スクリプトハンドルとはspawnやexecVMで呼び出されたスクリプトを識別するためのものです。
スクリプトを識別することで、スクリプトの完了を確認したり、スクリプトを強制的に止めることが可能になります。
「samplehandle = [ shinmai, 24 ] execVM "displayAge.sqf";」という形でスクリプトを実行した場合、
samplehandleという変数にそのスクリプトハンドルが代入されます。

waitUntil { scriptDone samplehandle };
hint "Script done.";

このようなスクリプトを書けば、スクリプト処理が完了した時点で「Script done.」と表示させることが出来ます。
また、次のように書くことでスクリプト処理を中断させることが出来ます。

terminate samplehandle;

今回のスクリプトは短いので中断させる必要はありませんが、
ループ処理で止めるタイミングをつかみづらいときはこうしたコマンドで止められることを覚えておきましょう。

制御文を使う

スクリプトファイルを使う場合に非常に便利なのが制御文です。
if文やfor文、forEach文やwhile文など様々なものがあります。
ループ処理をうまく使いこなすと複雑な処理を実現できます。

_score = 0;
_limit = 30;

while { _score < _limit } do {
  waitUntil { flag_getScore };
  _score = _score + 1;
  if( _score < 10 ) then {
    { _x setdammage 0; } forEach units group player;
  };
  flag_getScore = false;
};

上のサンプルスクリプトを見てみましょう。
_scoreは点数用の変数、_limitは目標点数だとします。
flag_getScoreは点数加算の条件を表すフラグ(boolean値)で、スクリプト外で値が変化し、trueになったときに点数加算状態とします。
最初に点数を初期化し、目標点数が設定されます。
その後は点数が目標点数になるまでwhile文の処理を繰り返すことになります。
ループの中で最初にwaitUntil文があり、これはブロック内の条件を満たすまでスクリプトを一時停止させます。
ここでは点数が入る状態になるまでスクリプトを一時停止させます。
点数が入る状態になったら点数を1加算します。
if文によって点数が10点に満たないときは追加の処理がなされます。
ここではプレイヤーのグループ全員のダメージを全快状態にします。
その後、点数が入る状態をfalseにリセットします。
そして次の点数が入る状態までスクリプトが一時停止する、という流れを繰り返します。

上の例はサンプルなのでわざとらしくループとか分岐を使っていますが、
こうした処理を書くことでフラッグ戦やデスマッチ等の処理は作られています。
ループや分岐を使うことで、新しいゲーム性を作り出すことが出来るので、
是非、オリジナリティに富んだミッションを作ってもらいたいと思います。


Homeへ