Arma 3のスクリプトとかミッション作成とか

Arma3のミッション作成とスクリプティング

intel回収スクリプト

intel回収を演出するスクリプト

intelにしたいオブジェクトのinit欄に呼び出しコマンドをコピペして使用してください。
intel回収時にキャラクターがintelを拾うアニメーションを実行します。

コードをダブルクリックすると全選択できます。

通常版

/* ------------------------------------------------------------------------------------------------
Script File: collectIntel.sqf

Description:
- Intelにしたいオブジェクトを回収可能にするスクリプト
- 回収時にアニメーションが実行されます

Parameter(s):
- _intel	: <OBJECT> アクションをアタッチするオブジェクト

Example:
- [this] call compile preprocessFileLineNumbers "collectIntel.sqf"

Returns:
- Nothing

Author: 
- Kirimochi.S
------------------------------------------------------------------------------------------------ */
params ["_intel"];

private _actionTitle = "<t <t size='1.5' color='#ffe100'><img image='\a3\ui_f\data\IGUI\Cfg\Actions\take_ca.paa'/> Collect Intel</t>";
private _actionCond = "true"; // 回収アクションを表示するための条件

KS_fnc_GotIntel = {
	params ["_caller"];
	/* Intal回収時に実行したいコードがあれば以下に記述する */
	[format ["%1がIntelを回収した",name _caller]] remoteExec ["hint",[0,-2] select isDedicated,true];
};

KS_fnc_takeIntel = {
	params ["_intel", "_unit"];

	private _posture = stance _unit;
	private _anim = "";

	switch (_posture) do {
		case "STAND":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPercMstpSnonWnonDnon_Putdown_AmovPercMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPercMstpSrasWrflDnon_Putdown_AmovPercMstpSrasWrflDnon"; };
				case "lnr": { _anim = "AinvPercMstpSrasWlnrDnon_Putdown_AmovPercMstpSrasWlnrDnon"; };
				case "pst": { _anim = "AinvPercMstpSrasWpstDnon_Putdown_AmovPercMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPercMstpSoptWbinDnon_Putdown_AmovPercMstpSoptWbinDnon"; };
			};
		};
		case "CROUCH":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPknlMstpSnonWnonDnon_Putdown_AmovPknlMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPknlMstpSrasWrflDnon_Putdown_AmovPknlMstpSrasWrflDnon"; };
				case "lnr": { _anim = "AinvPknlMstpSrasWlnrDnon_Putdown_AmovPknlMstpSrasWlnrDnon"; };
				case "pst": { _anim = "AinvPknlMstpSrasWpstDnon_Putdown_AmovPknlMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPknlMstpSoptWbinDnon_Putdown_AmovPknlMstpSoptWbinDnon"; };
			};
		};
		case "PRONE":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPpneMstpSnonWnonDnon_Putdown_AmovPpneMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPpneMstpSrasWrflDnon_Putdown_AmovPpneMstpSrasWrflDnon"; };
				case "lnc": { _anim = "AinvPpneMstpSnonWnonDnon_Putdown_AmovPpneMstpSnonWnonDnon"; };
				case "pst": { _anim = "AinvPpneMstpSrasWpstDnon_Putdown_AmovPpneMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPpneMstpSoptWbinDnon_Putdown_AmovPpneMstpSoptWbinDnon"; };
			};
		};
		default {};
	};

	if !(_anim isEqualTo "") then {
		_unit playMove _anim;
		waitUntil {animationState _unit == _anim || !alive _unit};
		waitUntil {animationState _unit != _anim || !alive _unit};
		if (alive _unit) then {
			[_intel] remoteExec ["DeleteVehicle",2];
		};
	} else {
		[_intel] remoteExec ["DeleteVehicle",2];
	};
};

_intel addaction [
	_actionTitle,
	{
		params ["_target", "_caller", "_actionId", "_arguments"];
		[_target, _caller] call KS_fnc_takeIntel;
		[_caller] call KS_fnc_GotIntel;
	},
	nil,8,true,true,"",_actionCond,3
];

_intel allowDamage false;

nil

ACE3対応版

/* ------------------------------------------------------------------------------------------------
Script File: collectIntel.sqf

Description:
- Intelにしたいオブジェクトを回収可能にするスクリプト
- 回収時にアニメーションが実行されます

Parameter(s):
- _intel	: <OBJECT> アクションをアタッチするオブジェクト

Example:
- [this] call compile preprocessFileLineNumbers "collectIntel.sqf"

Returns:
- Nothing

Author: 
- Kirimochi.S

Required Addon
- ACE3
------------------------------------------------------------------------------------------------ */
params ["_intel"];

private _actionTitle = "<t <t size='1.5' color='#ffe100'><img image='\a3\ui_f\data\IGUI\Cfg\Actions\take_ca.paa'/> Collect Intel</t>";
private _actionCond = "true"; // 回収アクションを表示するための条件

KS_fnc_GotIntel = {
	params ["_caller"];
	/* Intal回収時に実行したいコードがあれば以下に記述する */
	[format ["%1がIntelを回収した",name _caller]] remoteExec ["hint",[0,-2] select isDedicated,true];
};

KS_fnc_takeIntel = {
	params ["_intel", "_unit"];

	private _posture = stance _unit;
	private _anim = "";

	switch (_posture) do {
		case "STAND":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPercMstpSnonWnonDnon_Putdown_AmovPercMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPercMstpSrasWrflDnon_Putdown_AmovPercMstpSrasWrflDnon"; };
				case "lnr": { _anim = "AinvPercMstpSrasWlnrDnon_Putdown_AmovPercMstpSrasWlnrDnon"; };
				case "pst": { _anim = "AinvPercMstpSrasWpstDnon_Putdown_AmovPercMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPercMstpSoptWbinDnon_Putdown_AmovPercMstpSoptWbinDnon"; };
			};
		};
		case "CROUCH":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPknlMstpSnonWnonDnon_Putdown_AmovPknlMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPknlMstpSrasWrflDnon_Putdown_AmovPknlMstpSrasWrflDnon"; };
				case "lnr": { _anim = "AinvPknlMstpSrasWlnrDnon_Putdown_AmovPknlMstpSrasWlnrDnon"; };
				case "pst": { _anim = "AinvPknlMstpSrasWpstDnon_Putdown_AmovPknlMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPknlMstpSoptWbinDnon_Putdown_AmovPknlMstpSoptWbinDnon"; };
			};
		};
		case "PRONE":
		{
			private _wpn = ["non", "rfl", "lnr", "pst", "bnc"] param [["", primaryWeapon _unit, secondaryWeapon _unit, handgunWeapon _unit, binocular _unit] find currentWeapon _unit,"non"];
			switch (_wpn) do {
				case "non": { _anim = "AinvPpneMstpSnonWnonDnon_Putdown_AmovPpneMstpSnonWnonDnon"; };
				case "rfl": { _anim = "AinvPpneMstpSrasWrflDnon_Putdown_AmovPpneMstpSrasWrflDnon"; };
				case "lnc": { _anim = "AinvPpneMstpSnonWnonDnon_Putdown_AmovPpneMstpSnonWnonDnon"; };
				case "pst": { _anim = "AinvPpneMstpSrasWpstDnon_Putdown_AmovPpneMstpSrasWpstDnon"; };
				case "bnc": { _anim = "AinvPpneMstpSoptWbinDnon_Putdown_AmovPpneMstpSoptWbinDnon"; };
			};
		};
		default {};
	};

	if !(_anim isEqualTo "") then {
		_unit playMove _anim;
		waitUntil {animationState _unit == _anim || !([_unit] call ace_common_fnc_isAwake)};
		waitUntil {animationState _unit != _anim || !([_unit] call ace_common_fnc_isAwake)};
		if ([_unit] call ace_common_fnc_isAwake) then {
			[_intel] remoteExec ["DeleteVehicle",2];
		};
	} else {
		[_intel] remoteExec ["DeleteVehicle",2];
	};
};

_intel addaction [
	_actionTitle,
	{
		params ["_target", "_caller", "_actionId", "_arguments"];
		[_target, _caller] call KS_fnc_takeIntel;
		[_caller] call KS_fnc_GotIntel;
	},
	nil,8,true,true,"",_actionCond,3
];

_intel allowDamage false;

nil

スクリプトのサンプル

思いついたときに随時更新

オブジェクトにArsenalを追加する

this addAction ["Arsenal",{["Open",true] call BIS_fnc_arsenal;}];

オブジェクトにACE Arsenalを追加する

[_box, true] call ace_arsenal_fnc_initBox;

ダメージ無効化

this allowDamage false;

移動停止1

this disableAI "PATH";

移動停止2

this disableAI "MOVE";

こちらは下半身の動きが固定される。

オブジェクトを削除する

deleteVehicle _object;

人も車両もオブジェクトもトリガーもこれ
※プレイヤーは削除不可

変数とスコープ その1

変数とスコープは曖昧なままにしておくと痛い目にあいます。
今回は「変数」と「スコープ」を簡単に説明します。

そもそも変数とスコープって何?

変数とは

変数とはデータを格納する記憶域と変数名という識別子を合わせ持ったものです。
言わば、名前の書かれた箱のようなものです。

myVariable = 1;

この例では「myVariable」という変数に「1」というデータが代入されています。
変数に値を代入することを「初期化」と言います。

また、変数には一つのデータしか入ることはできず、

myVariable = 100;
myVariable = "Hellow";

とすると、変数「myVariable」に最初に代入された「100」という数値データは「"Hellow"」という文字列データに上書きされ、100という数値データは消えてしまいます。

変数は繰り返しの呼び出しや上書きを繰り返す事ができます。
しかし、異なるデータや関係性のない名前付けをすると難解なコードになってしまいます。
ですので、変数名はどうのようなデータが入るのかを分かりやすくし、異なる用途のデータで上書きすることは避けましょう。

変数を削除するには

nilを代入することで変数を削除することができます。

myVarible = nil;

ミッション中などで再度利用しないグローバル変数などは削除したほうがベターです。

スコープとは

プログラミングで言うスコープとは、銃などに付ける照準器ではなく、可視範囲という意味で使われます。
変数は定義されたスコープ内でのみ参照することができます。

グローバルスコープ

グローバル変数は、定義されたコンピュータ内で利用できる変数です。
ミッションエディターでユニットに付けられた名前(Variable Name)もグローバル変数であり、再定義または変更することはできません。

ローカルスコープ

ローカル変数は、定義されたスクリプト内やFunction、制御機構などの小さな領域のみで利用できる変数です。
ローカル変数は変数名の頭にアンダースコア(_)を付ける必要があります(例 "_myVariable = 0;")。

パブリックスコープ

パブリック変数は、ネットワーク内に存在する全てのコンピュータで利用できる変数です。
グローバル変数を"publicVariable"などでネットワーク上の他のPCと共有することでパブリック変数となります。

(おまけ)タグ付けについて

グローバル変数を作成する場合は単純な名前ではなく、タグ付をして重複を回避しましょう。

myVariable = true;  // Not Good
KS_var_myVariable = true; // GOOD

myFunction = { someCode };  // Not Good
KS_fnc_myFunction = { someCode }; // GOOD

execVMとspawnとcall

スクリプトを学ぶ上で何も考えなければ簡単だけど、気にしだすとワケワカメになる3つの呼び出しコマンド。

とりあえず初学者はexecVMでスクリプトファイルを呼び出すのが安牌。

spawnやcallは主にFunctionを呼び出すときに使用され、公式WikiのFunctionのページにどちらを使うかは書いてあるので、それに従えばOK。

以下、気になる人向け

execVM

execVM - Bohemia Interactive Community

execVMとcall, spawnの決定的な違い

spawnとcallはファンクションなどのコードを呼び出しますが、exevVMはスクリプトファイル(ex. script.sqf)の呼び出しのみ可能です。

execVMはスクリプトファイルをコードに変換し呼び出しています。

execVM "someScript.sqf"

この呼出コマンドは

spawn compile preprocessFileLineNumbers "someScript.sqf"

と同じ働きをします。

同じファイルを連続して呼び出すのは良くない

execVMは一度のみ読み込まれるスクリプトならば問題ありませんが、何度も呼び出すようなスクリプトで使用すべきではありません。
なぜなら、execVMは使用する度に呼び出し(spawn)と2つの変換作業(compile, preprocessFileLineNumbers)をさせてしまっているからです。

何度も呼び出すスクリプトはファンクションとして変数に保存し、その変数を呼び出すのが効率的です。
あまり良くない例

[_argument1] execVM "someScript.sqf";
[_argument2] execVM "someScript.sqf";
[_argument3] execVM "someScript.sqf";

改善例

KS_fnc_someFunction =  compile preprocessFileLineNumbers "someScript.sqf";
[_argument1] call KS_fnc_someFunction;
[_argument2] call KS_fnc_someFunction;
[_argument3] call KS_fnc_someFunction;

someScript.sqfのファイル内容を変数KS_fnc_someFunctionに保存し、それをファンクションとしてcallから呼び出しています。
こうすることで、変換作業を一度で済ませることができるので、CPUへの負担を減らすことができます。
(本来ならforEachなどの繰り返し構文で記述するべきですが、簡潔さのために割愛しました)

また、execVMは変換作業を必要とするために動作に若干の遅れが生じることがあります。
一度Functionとして格納し、それを呼び出すことでスムーズに動作させることが期待できるでしょう。

Call と Spawn

call - Bohemia Interactive Community
spawn - Bohemia Interactive Community

ポイント① 遅延が許可されているかどうか

callとspawnの決定的な差は、sleepやwaitUntilといった待機コマンドを使用することができるかどうかです。

  • callは待機コマンドを使用することができない。
  • spawnは待機コマンドを使用することができる。

この差はなぜなのかを簡単に説明します。
callの動きとspawnの動きを簡単な図で表すと次のようになります。

main ------> call === ------> end
    └> spawn ===

call(赤の流れ)はコマンドが処理されるのを今か今かと待ち受ける待機列で、そこでは遅延(=待機コマンド)の使用は許されません。
spawn(青の流れ)は黒の流れと並行して処理される部分で、遅延が許可されています。

せやなら自由度の高いspawnの方が便利やんけ!ってなるかもしれませんが、欠点もあります。

  • 同時に処理する項目が増えることでCPUへの負荷が大きくなる。
  • 処理される順番が記述された順通りになるとは限らない。

同時処理数は意図的でない限り少ないほうが良いので、なるべくcallを使うようにしましょう。

ポイント② 返り値の種類が異なる

callは呼び出されたファンクションの最後の値が返ってくる。
spawnスクリプトハンドルが返ってくる。

例えば次ようなコードをデバッグコンソールで実行すると、hintでプレイヤーの名前が表示されます。

KS_fnc_getPlayerName = {
    params ["_unit"];
    private _name = name _unit;
    _name
};

private _returnValue = [player] call KS_fnc_getPlayerName;
hint str _returnValue;

このコードのcallをspawnに変えた次のコードを実行すると

KS_fnc_getPlayerName = {
    params ["_unit"];
    private _name = name _unit;
    _name
};

private _returnValue = [player] spawn KS_fnc_getPlayerName;
hint str _returnValue;

次ようなコードそのものが表示されてしまいます。

params ["_unit"]; private _name = name _unit; _name

これはScript Handleというもので、コードの識別に使用されるものです。
ScriptDoneやterminateなどのコマンドで使用されます。

つまり、callやspawnはコードを実行するだけでなく、返り値を利用するために使い分けるということもあります。

これを念頭に置いて考えると、スクリプトの記述例によくある

nul = [] execVM "someScript.sqf";

といった「nul =」というのは返り値を格納するための変数というのがよくわかりますね。

EDEN Ediforなどのinit欄に挿入するコマンドでは利用しない返り値を「nul」や「0」で処理します。
スクリプトファイルではこの処置は必要ありません

その他

EDEN Editorのinit欄やデバッグコンソールはcallで呼び出されるので、sleepやwaitUntilなどは使用できません。

nul = [] spawn { code };

のように記述しましょう。




Arma3 Scriptを学び始める方へ その1

このブログについて

本ブログでは、プログラミング経験ゼロからArma3スクリプトを学びたい人に向けた解説を行います。
私自身も経験ゼロから独学でミンション作成やスクリプティングを学んできました。
何かしらの誤りや不備があればコメントにてご指摘ください。

スクリプティング事始め

BIKIを使おう

Arma 3のスクリプトはミッション作成者にとって避けては通れないものです。

スクリプトが書けなくてもEden Editorのオプション項目や3den Enhanced Addonの機能を使えばなんとかはなりますが、機能を理解するためにはスクリプトの知識がある程度は必要になります。

知識を付けるためにまず覚えてほしいのが、Arma 3の開発会社が運営する「Bohemia Interactive Community Wiki(通称BIKI)」の存在です。

Arma3 ユーザーはこのBIKIの2つのリストと長く付き合って行くことになります。

また、BI forumというユーザー同士の交流の場もあり、そこから多くの情報を得ることができます。
言語は英語のみですが、Google翻訳などを使えば大体の意味は読み取れます。

さらに、個人のブログなどでもスクリプトの紹介や解説などを行っているサイトがあるので、これらもどんどん活用していきましょう!

メモを取ろう

便利なスクリプトコマンドは何度も繰り返し使用されます。
例えば

this disableAI "MOVE";

のような、AIの動きを制限するコマンドは高頻出です。
このような簡単なコマンドなら書き残すまでもないかもしれませんが、

{ _x disableAI "MOVE"; } forEach units group this;

のように、複雑なスクリプトはメモからコピペするのがオススメです。

メモは自分が使いやすいツールを使用するのが最善ですが、
そういったツールがないよって方にはGoogle スプレッドシートをオススメします。

まとめ