ACSの構造

ここでは、ACSの構造や文法を紹介します。
かなり長くなってしまいました・・・しかもかなり読みにくいです(^_^;)
間違った記述や、意見がありましたらメールで連絡お願いします。


基本的な構造
関数について
Scriptの種類
include文
コメントの挿入
変数宣言
変数の宣言方法
配列変数
2進数
演算子
変数の値を画面に出力
print関数
文字の入力
エスケープシーケンス
文字の色の変更
文字列を扱う変数と整数を扱う変数について
フォントの変更&画像を画面に出力(SetFont関数)
HudMessage関数
SetHudSize関数
定数宣言
if文、switch文、ループ文(while文、for文など)
if文
switch文
while文
for文
ループ文の脱出、繰り返しなど
スコープ
関数の作成
ZDOOMには搭載されていない関数
具体例

基本的な構造

今回は、次のようなACSで解説を行います。


#include "zcommon.acs"

Script 1 ENTER
{
GiveInventory("Shotgun", 1);
}



関数について

このACSを実行すると、ゲームが開始されるとShotgunをプレイヤーが持っているはずです。
それではこのACSを詳しく見てみましょう。

GiveInventoryは指定したInventory(アイテム)を指定した個数だけプレイヤーに与えるという関数です。
このように、関数を使っていってゲームに影響を与えていきます。

ではこの関数についてもう少し調べてみましょう。
この関数の一般の形は


void GiveInventory(str inventory_item, int amount);


str inventoryとint amountを関数GiveInventoryに入力することで、それに応じた結果をこの関数が出すことができます。
str inventoryとint amountのような入力値の役割を果たすものを引数(ひきすう)を言います。

ここで

strとは、引数inventoryが文字列のデータ型であることを示しています。
同様に
intとは、引数amountが32bitの整数のデータ型であることを示しています。

例文のGiveInventory("Shotgun", 1)と比較しても、確かに前者が文字列で、後者が整数であることがわかります。


また

voidの部分が表す意味は、この関数が結果として出す値の種類(整数や文字列など)を表しています。
また、関数が結果として出す値を戻り値と言います。

今回はこの関数が結果として何も出さないのでvoid型となっています。
戻り値を返す関数の例として、関数ThingCountを上げると、

int ThingCount(int type, int tid)

この関数は、Spawn numbers(int type)とTid(int tid)で指定したThingの数を整数で返す関数です。
確かに戻り値のデータ型が32bitの整数のint型になっています。


Scriptの種類

Scriptの形を少しだけ紹介しておきます。
関数の作成でも紹介します。

Scriptを定義するときは次のように定義します


Script (番号) (条件または引数)
{
//内容
}


(番号)でScriptの番号を宣言し、(条件または引数)でScriptが起動する条件またはそれが無い場合、引数を宣言します。
Scriptの番号は1MAPにつきかならず唯一の番号にして下さい。
また、Scriptの中にある関数などは(ACS全体もそうですが、)左から右へ、上から下へ起動します。
基本的に上から下へ関数を書いていきましょう。その通りの順番に関数が起動します。
Scriptの起動する条件を紹介します。

種類の名前 このScriptの起動主 起動する条件
OPEN World このScriptがあるMAPが読み込まれたらすぐに起動します。1つのMAPにつき1回だけ起動されます。
ENTER Player プレイヤーがゲームに参加したときにそれぞれのプレイヤーに対して1回ずつ起動します。
RETURN Player プレイヤーがhub設定で繋がっているMAPから戻ってきたときに起動します。
hubの詳しい説明はMAPINFOの解説を見て下さい。
RESPAWN Player プレイヤーがマルチプレイヤーゲームで復活したときに起動します。
DEATH Player プレイヤーが死んだときに起動します。
LIGHTNING World このScriptがあるMAPにlightning設定(稲妻が光る設定)がある場合、稲妻が光るたびにこのScriptが起動されます。
lightningの詳しい説明はMAPINFOの解説を見て下さい。
UNLOADING World このScriptがあるMAPが終了したとき(ゴールしたとき)起動します。何回でも起動できます。
DISCONNECT World マルチプレイヤーゲームでプレイヤーがゲームを終了したとき(サーバーから離れたとき)に起動します。引数をかならず一つ宣言しなければなりません。この引数に自動的に(プレイヤー番号 - 1)が代入されます。


Script 1 ENTER
{
//内容
}


Script 999 (int gone) DISCONNECT//引数の宣言
{
PrintBold(s:"Player ", n:gone + 1, s:" quit the game");
}


条件を使う必要が無いScriptはClosed Scriptと呼ばれ、引数を(条件または引数)に持ってくることができます。
つまり、Scriptを実行する関数に、あらかじめ引数を入力しておくと、その値をそのまま実行するScriptで使うことができます。
例えば次のようなScriptのとき、


Script 1 (int a,int b)
{
//内容
}


代入された変数aと変数bをScript 1 の中で使うことができます。
関数の様に引数があることに注目して下さい。
ここで、宣言された変数a,bはこのScript内でしか使えないことに注意して下さい。
また、引数を使用しない場合は


Script 1 (void)
{
//内容
}


という風に入力しましょう。
しばらくの間、このページではScriptには引数を使いません。


また、Scriptを実行する方法を少し紹介しておきます。(ACS_Execute関数)

一般的な形は

ACS_Execute (script, map, s_arg1, s_arg2, s_arg3);

引数scriptは実行するScriptの番号を指定します。
引数mapは実行するScriptが含まれているMAP番号を指定します。同じMAPのScriptを使う場合は0を入力して下さい。(専ら0だと思います。)
引数s_arg○は引数を入力します。


例1

Script 1 ENTER
{
//内容

ACS_Execute (2, 0, 0, 0, 0);

//内容
}

Script 2 (void)
{
//内容
}
//ACS_Executeが実行されると、Script2が実行されます。


例2

Script 1 (int a,int b)
{
//内容
}

Script 2 (void)
{
//内容

ACS_Execute (1, 0, 3, 4, 0);

//内容
}


例2のとき、Script2を実行したとすると、ACS_Execute関数が実行されたとき、Script1が実行され、a,bにそれぞれ3,4が代入されます。


また、MAP上のLineなどからScriptを実行することもできます。
DeePseaの場合、Lineの編集画面を開いて、
Type→D ACS Scripts→1 Script Run +Script-Map-Arg1-Arg2-Arg3[80]
と選び、ACS_Execute関数と同様に入力すると、そのLineを起動すればそれに対応するScriptを実行させることができます。


include文

先ほど紹介したACSを使用します。


#include "zcommon.acs"

Script 1 ENTER
{
GiveInventory("Shotgun", 1);
}


先ほどの解説では関数GiveInventoryを使いましたが、このACSをコンパイルするときに、関数GiveInventoryのもとの情報が必要になってきます。
ここで必要になってくるのがinclude文です。

#include "名前"

ZDOOMでは
#include "zcommon.acs"
だけでほぼ十分だと思います。

ちなみに、テキストエディタでzcommon.acsを開いてみるとわかりますが

#include "zspecial.acs"
#include "zdefs.acs"
#include "zwvars.acs"

という文がzcommon.acsの中にあるはずです。
これら3つのacsファイルはそれぞれの役割があるので紹介しておきます。

zspecial.acs Action specialsの定義元です。
zdefs.acs すでに定義されている定数の定義元です。ここに新しく自分だけの定数を追加することもできます。
zwvars.acs scopeがworld型のオリジナルの変数をここで宣言します。※未確認

※以後、このページの「例」に出てくるScriptにinclude文がない例もありますが、実際はACSの先頭に#include "zcommon.acs"と書いてあります。


コメントの挿入

ACSでは、Scriptの要所要所にコメントを付け加えることができます。
各Scriptや関数に説明を付け加えることで、より見やすいScriptを作ることができます。
次のような2つのACSを別々のMAPで動かしたとします。


#include "zcommon.acs"

Script 1 ENTER
{
print(s:"Comment Test");
}


#include "zcommon.acs"

/*This is a comment*/

Script 1 ENTER
{
//日本語でもOKです。

print(s:"Comment Test");
//"Comment Test"とゲーム画面に表示

/*
こんなコメントも可能です。
*/
}


結果は両方とも同じで、ゲームが開始されると、"Comment Test"と表示されます。
このように、コメントはゲームの進行には一切影響を与えません。

ACSには、コメントの挿入する方法が2種類あります。//と/* */です。

//コメント その行の//より後の部分がすべてコメントになります。
/*
コメント
*/
/*と*/で挟まれた部分がコメントになります。
/*と*/の間が改行されていてもされていなくても間はコメントとなります。

また、コメントには日本語を持ってくることもできます。


変数宣言

変数に自由な値を代入することで、より複雑なScriptを作成することができます。


変数の宣言方法

変数は次の様にして宣言します。


(変数の形) (変数名);


(変数の形)で、その変数が扱うデータの形を指定します。
ZDOOMではint型とstr型などが使われます。
(変数名)は自分の好きな変数の名前を決めます。
必ず唯一の名前にしてください。
また、一度に複数の変数を宣言したいときは


(変数の形) (変数名1),(変数名2),・・・,(変数名n);


のように、","(コンマ)をはさんで宣言しましょう。

変数の形 取り扱うデータの種類
int ビット演算や算術演算などで使用します。
32bit(4Byte)の情報量の整数を取り扱います。(-2147483648〜2147483647の値が使用できます。)
また、文字コードも代入することができます。
また、FixedPointValueとして扱うこともできます。
FixedPointValueの詳しい説明はSpecial&関数集を見て下さい。
bool 論理演算で使用します。
TURE(真)、FALSE(偽)を扱うブール代数のように変数を使うことができますが、内容はintと同じで、0や1以外の値を代入しても、そのままの値になります。
str 文字列を取り扱います。


int MyVariable;

int a,b;
//int型の変数aとbが宣言されます。

str MyStrings;

bool BooleanTest;


また、変数を宣言するときについでに値を代入することができます。
そのことを初期化と呼びます。
ZDOOMの場合、変数を初期化しなかったら値は自動的に0が代入されます。


int Health = 100;

int a = 100,b = 50;
//int型の変数aとbが宣言され、aには100が、bには50が代入されます。

str Weapon = "Shotgun";

int MyCharacter = 'a';
//文字コードの代入(aを表す番号を代入しています。)


※ちなみに、余談ですが、実はint型とbool型とstr型はすべて整数の値を取り扱っています。
つまり、str型である文字列の形を扱う変数に整数を代入してもint型の整数と同じように使用することができます。
ACSを作るときに変数の役割をわかりやすくするためにこのように変数の形を形式的に分けているのだと思われます。
詳しくは変数の値を画面に出力の項目を見て下さい。


配列変数

配列変数を使用することで、一度に複数の変数が宣言できるだけでなく、変数を番号で呼び出すことが可能になり、非常に便利です。

まず、宣言方法を紹介します。


(変数の形) (変数の名前)[要素の数];


int MyArray[3];
//配列変数MyArrayに3個の変数(要素)があると宣言しました。


この例を使って説明します。
まず、配列変数MyArrayがint型であると宣言しました。
MyArrayの右にある[3]は、int型の変数MyArray[0],MyArray[1],MyArray[2]を宣言したということを意味しています。
これらを要素と呼びます。

配列変数MyArrayについて、
int MyArray[3];
と宣言すると→
MyArrayの1番目の要素 MyArray[0]
MyArrayの2番目の要素 MyArray[1]
MyArrayの3番目の要素 MyArray[2]

つまり、表より、配列変数の2番目を呼び出したいときは
MyArray[1]
と書くと、呼び出すことができます。
また、要素を決定するための番号は0から始まるため、n番目の要素を呼び出したいときは(例では、1 <= n <= 3)
MyArray[n - 1]
となることに注意して下さい。

要素は、普通の変数と同じように扱うことができます。
つまり、
MyArray[1] = 1 + 3;
としたら、要素MyArray[1]の値は4になります。

※注意:配列変数の宣言は、Scriptの外で行いましょう。(MapScopeで宣言しましょう。)
※注意:要素はScriptの括弧の中でしか使えません。
※注意:配列変数には文字列を代入することができます。


配列変数の初期化の方法は


(変数の形) (変数の名前)[要素の数] = {値1,値2,...,最後の要素の値};


int Numbers[5] = {2,7,1,8,2};
//一度にすべての要素の値を決めることができます。


ここで注意すべき点は、要素の値を上の例のように一度にすべて決められるのは、配列変数を宣言したときだけであることです。
つまり、


int Numbers[5];

Numbers[5] = {2,7,1,8,2};
//間違った例


のようにすることはできません。また、要素の値はSciprtの括弧の中でしか使用できません。
この解決策として


例:解決策

int Numbers[5];

Script ○○ OPEN//○○は使っていないScript番号
{
Numbers[0] = 2;
Numbers[1] = 7;
Numbers[2] = 1;
Numbers[3] = 8;
Numbers[4] = 2;
}//Scriptの中で値を変更


とする方法がありますが、初期化した方が楽だと思います。


配列変数の2番目の5番目の要素などのように、多次元配列を宣言することもできます。

宣言方法は


(変数の形) (変数の名前)[要素の数1][要素の数2]...[要素の数n];


int array[2][5];
//2*5=10個の要素ができます。


また、初期化の方法は、具体例を挙げると、


int array[2][3] = { { 1 , 2 , 3 } , { 4 , 5 , 6 } };


ここで、値が6の要素を呼び出したいときは配列変数arrayの2番目の3番目の要素になるので、それぞれ値を1引くことに注意して


x = array[1][2];
//xに6を代入
//array[2番目 - 1][3番目 - 1]


配列変数の要素を呼び出すための数字の場所に変数を持ってくることもできます。
下の例を見て下さい。


int x = 3;//3番目の要素を指定します。
int y;//値を代入するための変数
int array[5] = {1,2,3,4,5};

Script 1 OPEN
{
y = array[x - 1];
}
//yに配列変数arrayの3番目の要素と同じ値を代入しました。


多次元配列の場合も同様に、変数を持ってくることができます。

具体例はこのページの最後にある具体例にありますが、ループ文を使用しているため、ここではまだ説明しません。


2進数

ZDOOMは32bitの大きさの整数を扱うことができます。
つまり、2進数で整数を表すと

00000000000000000000000000000000
から
11111111111111111111111111111111
まで
の表し方があります。
2進数で整数を表すとき、各位は0か1の2通りしか表し方が無いので、
ZDOOMで整数は2^32 = 4294967296 通りの表し方があります。
ここで、正負の記号を考えて、ZDOOMでは10進数で表すと

-2147483648
から
2147483647
まで
の整数を扱うことができます。

また、ZDOOMでは左から1bit目を正負の記号に割り当てています。
ここで、2進数と10進数の対応表を紹介します。

1ずつ値を増やしています。↓

2進数 10進数
00000000000000000000000000000000 0
00000000000000000000000000000001 1
00000000000000000000000000000010 2
・・・ ・・・
01111111111111111111111111111111 2147483647
10000000000000000000000000000000 -2147483648
10000000000000000000000000000001 -2147483647
10000000000000000000000000000010 -2147483646
・・・ ・・・
11111111111111111111111111111111 -1
00000000000000000000000000000000 0
・・・ ・・・

-1から0へ変わるときは1を足して(形式的に右から33bit目に)繰り上がると覚えるとわかりやすいと思います。


演算子

演算子を使用することで、変数の値を変えることができます。


演算方法
はじめに、整数を演算する方法を紹介します。
ZDOOMでは以下の紹介された演算方法以外の演算方法がありますが、そのような演算子が出た場合は演算子表で紹介します。


二項演算

二つの値を使用して計算をします。
ZDOOMで、二項演算の形は

(値) (演算子) (値)
//2 * 3など

の形となります。
また、ZDOOMで使用する二項演算子は、複数つなげて多項演算を行うこともできます。
//2 * 3 * 5など


単項演算

一つの値を使用して計算をします。
ZDOOMで、単項演算の形は

(演算子) (値)
//- 1など

の形となります。


代入演算

文字通り変数に整数を代入する演算です。
=が代入演算子です。
左辺が変数で、右辺が整数(や変数がある式)であるようにしましょう。


算術演算

通常の+,-などの演算子を使った演算方法です。
四則演算や剰余演算などに使用されます。


関係演算

2つの整数の大小関係や等号関係を演算する方法です。
>や<などが関係演算子です。

例えば

a < 3
などが関係演算です。


ビット演算

2つの整数(または変数)をビット演算する場合、
各整数をbit(2進数、ただし左から1bit目は正負の記号を表します。)で表して、
2つの整数のそれぞれ同じ場所のbitについて順番にすべてのbitを計算をします。
int型の整数の演算に向いています。
2進数の表し方は2進数を見て下さい。


論理演算

2つの整数(または変数)を論理演算する場合、
それぞれの整数の事柄が真(TRUE)か偽(FALSE)かで演算をします。
このとき、整数の値が0の場合は、偽(FALSE)と認識され、
0以外の場合は負の数の場合でも真(TRUE)と認識されます。
bool型(またはint型)の整数の演算に向いています。
また、事柄には演算子を使った式を持ってくることができます。

例えば
bool a = 1,b = 0;
とし、論理演算をするときは、
aは真(TRUE)の事柄でbは偽(FALSE)の事柄です。

また、
int monsters = ThingCount(0,999);//ここではtidが999のモンスターの数を数える役割の関数
とし、論理演算をするときは、
monstersに代入された値が0のとき(tidが999のモンスターが全滅したとき)、
変数monstersは偽(FALSE)の事柄となり、
monstersに代入された値が1以上のとき(tidが999のモンスターが生存しているとき)、
変数monstersは真(TRUE)の事柄となります。


次に、演算子を紹介します。

演算子 演算方法 役割 使用例や解説(ここで出てくるアルファベットは特に断りが無い限り、それぞれint型の変数とします。)
= 代入演算
二項演算
右辺にある値を左辺に代入します。
数学で使用する=とは意味が違うので注意して下さい。
(右のの式を見て下さい。)
左辺には変数を持ってきましょう。
x = 2;

x = 2 + 3;
//xに5を代入します。


x = x + 2;
//xの元々の値に2を足した値をxに代入します。
+ 算術演算
二項演算
+記号の左右の値を足します。 x = 2 + 3;
- 算術演算
単項演算
値の符号を逆にします。 x = -2;
- 算術演算
二項演算
-記号の左の項から右の項を引きます。 x = 5 - 2;
* 算術演算
二項演算
*記号の左右の値を掛けます。 x = 5 * 2;
/ 算術演算
二項演算
/記号の左の項を右の項で割ります。
また、ZDOOMで扱う値は整数なので、結果の値が小数の場合でも、切り捨てをした整数の値が計算の結果になります。
ちなみに、0でわるとエラーが発生します。
x = 4 / 2;

x = 3 / 2;
//結果は1.5を切り捨てた値の1です。
% 算術演算
二項演算
%記号の左の項を右の項で割ったときのあまりを計算します。
ちなみに、0でわるとエラーが発生します。
x = 12 % 5;
//結果は2になります。
& ビット演算
二項演算
ビット演算の演算子です。AND式(ビット論理積)を行います。
両方のbitが1であるときは1を返し、それ以外の場合は、0を返します。

書き方:
x = a & b;
など
a、bは1bitの情報量(0か1)の変数とします。
a b a & b
0 0 0
0 1 0
1 0 0
1 1 1

また、
x = 14 & 7;
のとき

14は2進数で
1110
7は2進数で
0111

よって各位でAND式をかけると
14 & 7 = 0110
(右辺は2進数)
となり、0110(2進数) = 6(10進数)となるので、
xの値は6となります。

(ZDOOMで扱う整数はは32bitですが、今回は残り28bit部分は何も影響しないので省いています)
| ビット演算
二項演算
ビット演算の演算子です。OR式(ビット論理和)を行います。
両方のbitのうち少なくとも片方が1であるときは1を返し、それ以外の場合は0を返します。

書き方:
x = a | b;
など
a、bは1bitの情報量(0か1)の変数とします。
a b a | b
0 0 0
0 1 1
1 0 1
1 1 1

また、
x = 12 | 5;
のとき

12は2進数で
1100
5は2進数で
0101

よって各位でOR式をかけると
12 | 5 = 1101
(右辺は2進数)
となり、1101(2進数) = 13(10進数)となるので、
xの値は13となります。

(ZDOOMで扱う整数はは32bitですが、今回は残り28bit部分は何も影響しないので省いています)
^ ビット演算
二項演算
ビット演算の演算子です。XOR式(ExclusiveOR、ビット排他的論理和)を行います。
両方のbitのうちどちらか片方が1であるときは1を返し、それ以外の場合(どちらとも1または0のとき)は0を返します。

書き方:
x = a ^ b;
など
a、bは1bitの情報量(0か1)の変数とします。
a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

また、
x = 9 ^ 10;
のとき

9は2進数で
1001
10は2進数で
1010

よって各位でXOR式をかけると
9 ^ 10 = 0011
(右辺は2進数)
となり、0011(2進数) = 3(10進数)となるので、
xの値は3となります。

(ZDOOMで扱う整数はは32bitですが、今回は残り28bit部分は何も影響しないので省いています)
~ ビット演算
単項演算
ビット演算の演算子です。NOT式(ビット否定)を行います。
整数を2進数に戻し、各bitでNOT式をかけます。
ある位が0のときは1にし、1のときは逆に0にします。

書き方:
x = ~a;
など
x = ~5;
のとき

ZDOOMでは整数を32bitの情報量で表すので
5は2進数で
00000000000000000000000000000101
各位について0と1を逆にするので
~5は2進数で
11111111111111111111111111111010
となります。
これを10進数で表すと
-6となります。
よって
xの値は-6になります。
2進数の表し方は2進数を見て下さい。
<< ビット演算
二項演算
ビット演算の演算子です。<<記号の左の項を2進数に直して、<<記号の右の項の値の分だけ左の項の値を左へ移動させます。

書き方:
x = a << 2;
など
今回は整数が4ビットの情報量しかないとします。

x = 2 << 1;
のとき

2は2進数で
0010
これを左へ1個分移動させるので
2 << 1は2進数で
0100
となります。
これを10進数で表すと
4となります。
よって
xの値は4になります。

また、
x = -7 << 1;
のとき、
-7は2進数で
1001
となります。
これを左へ1シフトすると
-7 << 1は2進数で
0010
となり、
xは2になります。
つまり、変数の一番左のビット部分の値が何であれ情報は消えてしまい、右から新しくやってくるビット部分は0になります。

ちなみに、
a << b

a * 2^b
と同じ意味になります。(ただし、aの一番左のビット部分が0で、そこに何も影響が無い場合に限ります。)
>> ビット演算
二項演算
ビット演算の演算子です。>>記号の左の項を2進数に直して、>>記号の右の項の値の分だけ左の項の値を右へ移動させます。

書き方:
x = a >> 3;
など
今回は整数が4ビットの情報量しかないとします。

x = 2 >> 1;
のとき

2は2進数で
0010
これを右へ1個分移動させるので
2 >> 1は2進数で
0001
となります。
これを10進数で表すと
1となります。
よって
xの値は1になります。

また、
x = 1 >> 1;
のとき、
1は2進数で
0001
となります。
これを右へ1シフトすると
1 >> 1は2進数で
0000
となり、
xは0になります。
つまり、変数の一番右のビット部分の値が何であれ情報は消えてしまい、左から新しくやってくるビット部分は0になります。

ちなみに、
a >> b

a / 2^b(小数点以下切り捨て)
と同じ意味になります。(ただし、aの一番左のビット部分が0の場合に限ります。)
== 関係演算
二項演算
==記号の左右の値が同じ場合、1(TRUE)を返します。
==記号の左右の値が違う場合、0(FALSE)を返します。

書き方:

bool x = a == b;

など
bool a = 3 == 3;
//この場合、値が等しいので、変数aには1が代入されます。

他にも
if(health == 100){
//内容
}

など、条件として使用することもできます。
!= 関係演算
二項演算
!=記号の左右の値が違う場合、1(TRUE)を返します。
!=記号の左右の値が同じ場合、0(FALSE)を返します。

書き方:

bool x = a != b;

など
bool a = 3 != 3;
//この場合、値が等しいので、変数aには0が代入されます。

他にも
if(monsters != 0){
//内容
}

など、条件として使用することもできます。
> 関係演算
二項演算
a > b
のとき、aがbより大きければ1(TRUE)を返します。
aがbより小さいまたは等しければ0(FALSE)を返します。

書き方:

bool x = a > b;

など
bool a = 4 > 3;
//この場合、4は3より大きいので、変数aには1が代入されます。

他にも
while(monsters > 0){
//内容
}

など、条件として使用することもできます。
>= 関係演算
二項演算
a >= b
のとき、aがbより大きいまたは等しければ1(TRUE)を返します。
aがbより小さければ0(FALSE)を返します。

書き方:

bool x = a >= b;

など
bool a = 3 >= 5;
//この場合、3は5より小さいので、変数aには0が代入されます。

他にも
if(ammo >= 50){
//内容
}

など、条件として使用することもできます。
< 関係演算
二項演算
a < b
のとき、aがbより小さければ1(TRUE)を返します。
aがbより大きいまたは等しければ0(FALSE)を返します。

書き方:

bool x = a < b;

など
bool a = 4 < 3;
//この場合、4は3より大きいので、変数aには0が代入されます。

他にも
while(x < 10){
//内容
}

など、条件として使用することもできます。
<= 関係演算
二項演算
a <= b
のとき、aがbより小さいまたは等しければ1(TRUE)を返します。
aがbより大きければ0(FALSE)を返します。

書き方:

bool x = a <= b;

など
bool a = 5 >= 5;
//この場合、値が等しいので、変数aには0が代入されます。

他にも
if(armor <= 50){
//内容
}

など、条件として使用することもできます。
&& 論理演算
二項演算
論理演算の演算子です。AND式(論理積)を行います。
両方の事柄が真(TRUE)ならば1を、それ以外の場合は0を返します。
また、"事柄"に、演算子を使った式を持ってくることができ、そのときはそちらの計算が優先されます。
日本語で言うと、"かつ"を意味しています。

書き方:

bool x = a == 1 && b != 1;
//aが1かつbが1でないなら1を返します。
など
a b a && b
3 && 0
の場合は
3→真
0→偽
なので0となります。
また、
a == 1&& b != 1&& c == 5
などと複数つなげても使用できます。
条件を指定するときなどに便利です。
|| 論理演算
二項演算
論理演算の演算子です。OR式(論理和)を行います。
両方の事柄のうち、どちらか少なくとも1つが真(TRUE)ならば1を、それ以外の場合は0を返します。
また、"事柄"に、演算子を使った式を持ってくることができ、そのときはそちらの計算が優先されます。
日本語で言うと、"または"を意味しています。

書き方:

bool x = a == 1 || b != 1;
//aが1またはbが1でないなら1を返します。
など
a b a || b
3 || 0
の場合は
3→真
0→偽
なので1となります。
また、
a == 1|| b != 1|| c == 5
などと複数つなげても使用できます。
条件を指定するときなどに便利です。
! 論理演算
単項演算
論理演算の演算子です。NOT式(否定)を行います。

!記号の右にある事柄が真(TRUE)なら0を、偽(FALSE)なら1を返します。
また、"事柄"に、演算子を使った式を持ってくることができ、そのときはそちらの計算が優先されます。

書き方:

bool = !(a == 1);
など
a !a
ちなみに、
!!3
とすると、
!3
で0になるので
!!3
は、
!0
となり、結果は
1
となります。
+= 算術代入演算
二項演算
x += 2;
とすると、
x = x + 2;
と等しいです。
x += 2;
//xの元の値に2を足した値をxに代入します。
-= 算術代入演算
二項演算
x -= 2;
とすると、
x = x - 2;
と等しいです。
x -= 2;
//xの元の値から2を引いた値をxに代入します。
*= 算術代入演算
二項演算
x *= 2;
とすると、
x = x * 2;
と等しいです。
x *= 2;
//xの元の値に2を掛けた値をxに代入します。
/= 算術代入演算
二項演算
x /= 2;
とすると、
x = x / 2;
と等しいです。
x /= 2;
//xの元の値を2で割った値をxに代入します。
%= 算術代入演算
二項演算
x %= 2;
とすると、
x = x % 2;
と等しいです。
x %= 2;
//xの元の値を2で割ったときのあまりをxに代入します。
&= ビット代入演算
二項演算
x &= y;
とすると、
x = x & y;
と等しいです。
x &= 5;
//xの元の値と5でビット論理積を行って結果の値をxに代入します。
|= ビット代入演算
二項演算
x |= y;
とすると、
x = x | y;
と等しいです。
x |= 5;
//xの元の値と5でビット論理和を行って結果の値をxに代入します。
^= ビット代入演算
二項演算
x ^= y;
とすると、
x = x ^ y;
と等しいです。
x ^= 5;
//xの元の値と5でビット排他的論理和を行って結果の値をxに代入します。
<<= ビット代入演算
二項演算
x <<= y;
とすると、
x = x << y;
と等しいです。
x <<= 5;
//xの元の値を左へ5シフトした値をxに代入します。
>>= ビット代入演算
二項演算
x >>= y;
とすると、
x = x >> y;
と等しいです。
x >>= 5;
//xの元の値を右へ5シフトした値をxに代入します。
++ インクリメント
単項演算
b = a++;
のとき、
aをbに代入してからaに1を足します。

b = ++a;
のとき、
aに1を足してからaをbに代入します。
a = 1のとき、
b = a++;
とすると、bは1に、

b = ++a;
とするとbは2になります。

(どちらも最終的にaは2になります。)

また、
a++;
だけでも使用できます。
このときは、aの値が1増えるだけです。
-- デクリメント
単項演算
b = a--;
のとき、
aをbに代入してからaから1を引きます。

b = --a;
のとき、
aから1を引いてからaをbに代入します。
a = 1のとき、
b = a--;
とすると、bは1に、

b = --a;
とするとbは0になります。

(どちらも最終的にaは0になります。)

また、
a--;
だけでも使用できます。
このときは、aの値が1減るだけです。

※注意:ZDOOMには、指数の演算子がありません。

※注意:演算をするときに、()を使用することができます。()の中にまた()入るような式になっても問題はありません。


変数の値を画面に出力

ACSの構造とは少し話がそれますが、変数の値や、文字列をゲーム画面に表示させてみましょう。
このコーナーではprint関数とHudMessage関数について紹介します。


print関数

print関数は指定した数字や文字列を(この関数の起動主の)画面に表示させる関数です。
では、print関数の一般の形を見てみましょう。

void Print(item(s));

voidは戻り値が何もないことを意味しています。
item(s)に、表示させたい文字列や、数字を入力します。
item(s)の一般的な形は、

<cast type>:<expression>

<cast type>で表示する文字列や数字の種類を指定し、<expression>で内容を指定します。
<cast type>と<expression>の間に:(コロン)があることに気をつけて下さい。


文字の入力

print(s:"This is a test");
//"This is a test"という文字列が画面の中央に表示されます。


この場合、<cast type>は's'の文字列型で、<expression>は"This is a test"という文字列です。

次に、<cast type>の種類を紹介します。

<cast type>に入れる文字 <expression>に入力する値と表示する文字の種類
a 文字コードを取り扱う配列変数を[ ](要素を指定する括弧)抜きで入力すると、要素の順番に文字が表示されます。(文字列ではないので注意して下さい。)
c 文字コードで表した文字を表示します。(1文字だけです。また、文字列ではありません。)
d 10進法の整数を表示します。
f FixedPointNumberを表示します。(小数の状態で表示します。)
i 整数を表示します。'd'と同じです。
k ゲームプレイヤーが割り当てているキーの役割を入力すると、割り当てているキーの名前が表示されます。
例: k:"+jump"など
l 不明です・・・同じWAD内にあるテキストファイルを開くのだと予想していたのですが、どうやら違うようです・・・'s'と同じように思われます。
n Thingの名前を表示します。
1〜8を入力すると、そのゲームに参加している(指定した番号の)プレイヤーの名前を表示します。もし、(指定した番号の)プレイヤーがゲームに参加していなければ"Player 番号"と表示されます。
それ以外の場合(入力する値が0や9以上のときなど)は、この関数の起動主の名前を表示します。もし、起動主がプレイヤーでなければ、そのThingのClassの名前を表示します。
s 文字列を表示します。

int a = 2;
int b = 1;

Script 1 ENTER
{
print(d:a + b);
//3が表示されます。
}


次に、複数の<cast type>と<expression>を同時に表示させる方法を紹介します。
その場合のprint関数の書き方は

void Print(<cast type1>:<expression1> , <cast type2>:<expression2> , ...);

このように、<cast type○>:<expression○>の後に,(コンマ)を付けてから次の<cast type>と<expression>を書きましょう。


Script 1 ENTER
{
int imp = 15;
print(s:"Destroy ",d:imp,s:" imps!");
}
//"Destroy 15 imps!"と表示されます。
//d:impの部分では数字しか表示されないので数字の前後の空白はその前後の文字列に入れています。


エスケープシーケンス

先ほど紹介した方法で、ほとんどの文字が使用できますが、"記号を文字として表示したり、改行などを行うことができません。
次の例を見て下さい。


"改行"の間違った使用例

Script 1 ENTER
{
print(s:"This is the first line
This is the second line");
//改行しました
}


このACSを実行してみると"This is the first line This is the second line"と改行されずに表示されてしまいます。

また、次の例も見て下さい。


"ダブルクォーテーションマーク"の間違った使用例

#include "zcommon.acs"

Script 1 ENTER
{
print(s:"You destroyed the "cyberdemon"");
}


このACSはコンパイラが"You destroyed the "をひとまとまりの文字列と考えてしまい、正常にコンパイルせずに、エラーが発生します。


このような特殊な文字を入力したいときに、エスケープシーケンスを使用します。
エスケープシーケンスの入力方法は

\○

です。○には文字を入れます。
ちなみに、外国では\の部分はバックスラッシュ(/が反対に傾いた記号)ですが、日本では\になっています。

○の部分に入れる文字のうち、一部を紹介します。

\○のの○の部分に入れる文字 表示される文字
n 改行します。
t 画面には空白と同じ役割を果たしますが、コンソールには正方形が表示されます。
r 画面には空白と同じ役割を果たしますが、コンソールには三角形が表示されます。
ただし、コンソールにおいて、\rが文字列の最後にあると、その文字列がある行がすべて消えます。
" ダブルクォーテーションマークを表示します。
\ バックスラッシュを表示します。
0 この文字より後に文字があったとしても、ここで文字列の表示を終了します。

専ら\nしか使わないと思います。では、先ほどの例をエスケープシーケンスを使って書き直してみましょう。


"改行"の例

Script 1 ENTER
{
print(s:"This is the first line\nThis is the second line");
//改行しました
}


"ダブルクォーテーションマーク"の例

#include "zcommon.acs"

Script 1 ENTER
{
print(s:"You destroyed the \"cyberdemon\"");
}


これで正常に表示されるはずです。


文字の色の変更

次に、文字の色を変えてみましょう。
変えたい文字列の前に\c○(○はアルファベット1文字)と入力するとそれより後ろの文字列の色を変えることができます。


print(s:"\cgThis is red \chThis is blue");
//"This is red"は赤色で、"This is blue"は青色で表示されます。


これも、エスケープシーケンスの一種だと思います。

色の種類を紹介します。

"\c○"の○に入るアルファベット 画像 colorに入る定数
(※)
定数の値
(※)
a 赤っぽい煉瓦色 CR_BRICK 0
b 黄褐色、小麦色? CR_TAN 1
c 灰色? CR_GRAY
または
CR_GREY
2
d 緑色 CR_GREEN 3
e 茶色 CR_BROWN 4
f 金色(明るい黄色) CR_GOLD 5
g 赤色 CR_RED 6
h 青色 CR_BLUE 7
i オレンジ色 CR_ORANGE 8
j 白色(灰色より微妙に白と黒の差が大きいです。) CR_WHITE 9
k 黄色 CR_YELLOW 10
l 文字として使われている画像の元々の色だと思います。 CR_UNTRANSLATED -1
m 黒色 CR_BLACK 12
n 水色 CR_LIGHTBLUE 13
o クリーム色 CR_CREAM 14
p オリーブ色 CR_OLIVE 15
q 暗緑色 CR_DARKGREEN 16
r 暗赤色 CR_DARKRED 17
s 焦茶色 CR_DARKBROWN 18
t 紫色 CR_PURPLE 19
u 暗い灰色 CR_DARKGRAY
または
CR_DARKGREY
20

ちなみに\c["色の名前"]でも色が指定できるようです。ただし、textcolorsに定義されている物のみのようです。
例:\c[green]
など

(※)HudMessage関数の引数colorで使用します


文字列を扱う変数と整数を扱う変数について

ちなみに、余談ですが、次の様な書き方をすると正しく文字列が表示されません。
(解説の都合上、ACSのすべての記述をしています。)


間違った例

#include "zcommon.acs"

Script 1 ENTER
{
print(s:"You got" + "the shotgun!");
}


このACSを実行すると、"the shotgun"と表示されます。

どうやらコンパイラがACSをコンパイルするときに、ACSとは別々に文字列を記憶して、上から出てきた文字列の順番に0,1,2...と番号を割り当て、さらに、割り当てたれた番号を文字列に代入するようです。
(ただしinclude文で使用する"(内容)"は数えません。また、定数宣言のときに、文字列を使った場合は、数えられます。)

また、ACSの中に内容が全く同じ文字列が複数現れた場合は、後にある方の文字列に割り当てられる番号は、最初にある同じ文字列に割り当てられる番号になります。

つまり、先ほどの例の

print(s:"You got" + "the shotgun!");

は、include文は考えないので、"You got"に0、"the shotgun!"に1を代入して

print(s:0 + 1);

となり、

print(s:1);

となります。ここで、番号1が割り当てられた文字列は"the shotgun!"なので、結果的に"the shotgun!"が画面に表示されます。
ちなみに、次の様な等号関係は成り立ちます。


#include "zcommon.acs"

Script 1 ENTER
{
str MyStrings = "equal";
print(d:MyStrings == "equal");
}


この場合、MyStringsと後に出てくる"equal"は同じ番号が割り当てられているので等号関係が成り立ち、画面に1と表示されます。


また、これらの関係から、str型の変数に整数を入れて四則演算をしたり、int型の変数に文字列を代入(正確には文字列の順番の整数)して文字列として使うこともできます。ちなみに、同様にbool型の変数もこのように扱えます。

つまり、ZDOOMでは、変数はすべて整数の値しか扱っていないことがわかります。


フォントの変更&画像を画面に出力

次に、文字のフォントを変えてみましょう。
文字のフォントを変えるにはSetFont関数を使用します。
SetFont関数は、現在のフォント(何も指定されていない場合は"smallfont")から指定した名前のフォントに変更し、そのSetFont関数があるScriptの中でのみ、そのSetFont関数より後の文字を表示させる関数に影響を与えます。
SetFont関数の一般の形は

void SetFont(str fontlump);

引数fontlumpに指定したいフォントの名前をstr型で入力します。

とりあえず具体例を見てみましょう。


Script 1 ENTER
{
SetFont("bigfont");
print(s:"This is \"bigfont\"");
//エスケープシーケンスを使ってみました。
}


ACSを実行してみるとわかりますが、いつもよりも大きめの文字が画面に表示されると思います。
ZDOOMには、最初から3種類のフォントが用意されていますが、オリジナルのフォントを作成すればそれらを利用することもできます。
最初から用意されているフォントを紹介します。

フォント名 特徴 画像
CONFONT コンソールで使われているフォントです。
SMALLFONT 小さめのフォントです。
SetFont関数を使用していない場合はこれが自動的に選択されます。
BIGFONT 大きめのフォントです。

では、次に通常の画像をprint関数とSetFont関数を使って表示してみましょう。
次の例を見て下さい。(IWADはDOOM2.wadを使用しています。)


Script 1 ENTER
{
SetFont("GATE1");
print(s:"A");
}


テレポーターの画像が画面に表示されれば成功です。
このように、SetFont関数に表示したい画像の名前を入力して、print関数(やそれに準ずる関数)に文字列"A"を表示させるようにすると、表示したい画像が表示されます。


HudMessage関数

print関数では文字を中央に表示させるだけでしたが、HudMessage関数では表示時間、表示する座標などを細かく設定することができます。ただし、HudMessage関数はprint関数と違って、引数で指示しない限り、画面で表示したメッセージがコンソールでは表示されません。
HudMessage関数の一般の形は

hudmessage (text; int type, int id, int color, fixed x, fixed y, fixed holdTime, ...);

textはprint関数で紹介したitem(s)の部分に相当します。
引数typeはメッセージの表示する方法を指定します。
引数idは表示したメッセージに番号を付けます。
引数colorは表示するメッセージの色を指定します。
引数xとyはメッセージを表示する座標を指定します。
引数holdtimeはメッセージを表示する時間を指定します。
また、引数typeの値によって、これより後に新たに引数を指定しなければならない場合があります。

ここでは、引数ごとに紹介していきます。


引数type

引数typeはメッセージを表示する方法を整数(専ら定数)で指定します。
zdefs.acsでは次の様な定数がすでに定義されているのでそのまま定数を使うことができます。

typeに入れる定数 説明 新たに増える引数 引数の説明
HUDMSG_PLAIN 0 そのままメッセージを描写します。 なし なし
HUDMSG_FADEOUT 1 文字が消えるときに、新たに増える引数fadetimeに入力した時間の分だけ時間をかけてゆっくりと消えていきます。(フェードアウト) fixed fadetime
(fixed point values)
メッセージが消え始めてから消え終わるまでのまでの時間を指定します。
単位は秒で、fixed point valueで指定します。
HUDMSG_TYPEON 2 メッセージを指定した間隔で一文字ずつ順番に表示していきます。タイピングをしているように見えます。 fixed typetime
(fixed point values)
メッセージの文字と文字を打つ時間の間隔を指定します。
単位は秒で、fixed point valueで指定します。
fixed fadetime
(fixed point values)
メッセージが消え始めてから消え終わるまでのまでの時間を指定します。
単位は秒で、fixed point valueで指定します。
HUDMSG_FADEINOUT 3 メッセージがフェードインして、しばらくしてからフェードアウトします。 fixed inTime メッセージが現れ始めてから完全に現れるまでのまでの時間を指定します。
単位は秒で、fixed point valueで指定します。
fixed outTime メッセージが消え始めてから消え終わるまでのまでの時間を指定します。
単位は秒で、fixed point valueで指定します。

※新たに増える引数はhudmussage関数の...の部分の引数にあたります。このページの解説で紹介している順番(上から下)に付け加えて下さい。また、付け加える必要が無ければ、この引数自体を書く問題はありません。
例:hudmessage (s:"argument"; HUDMSG_FADEOUT, 1, CR_RED, 0.5, 0.5, 5.0, 2.0);//この場合、新たに1つ引数を付け加えます。


さらにこれらにビット論理和(|演算子)を使用して、次の表示条件を付け足すことができます。
次の表を見るとわかりますが、確かにビット論理和を使用するのに都合の良い値になっていることがわかります。

typeに入れる定数 値(16進数) 値(2進数) 説明
HUDMSG_LOG 80000000 10000000000000000000000000000000 画面上で表示したメッセージをコンソール上でも表示させます。
HUDMSG_COLORSTRING 40000000 01000000000000000000000000000000 引数colorは通常、定数(整数)を入力しますが、この条件を付け加えると、色の名前を書いた文字列を定数の代わりに入力することができます。

引数id

引数idに整数を入力することで、画面に表示したメッセージに管理用の番号を付けることができます。
複数のHudMessage関数のメッセージが時間的に重なるように表示してしまうときに、idを決めておくと便利です。

idが0でないあるメッセージが最初に表示されているとします。そのidと同じメッセージが後から表示されると、前者のメッセージは後者のメッセージの表示と同時に(表示していれば)画面上から消えてしまいます。

また、idの値が小さいメッセージほどより前に表示されます。


引数color

全体の文字の色を指定します。部分的に変えたい箇所があればprint関数のときと同様に、文字列に\c○(○は対応するアルファベット)と入力して色を変えることもできます。\c○で色を何も指定していない場合は、引数colorで指定した色が表示されます。
詳しくはprint関数の文字の色の変更の表を見て下さい。


引数xとy

これらの引数でメッセージを表示する座標(それぞれx座標、y座標)を指定しますが、少し複雑です。
基本的には0.0〜1.0のように、小数単位で考えますが、整数部分が変わったりマイナス記号がついたりします。
xやyの値の次の表を参考にして下さい。

引数xについて(横方向)

xの範囲 指定したx座標を基準にするもの 値の増減と位置の関係 改行されたメッセージの表示方法
0.0 <= x <= 1.0 メッセージの中心を通る部分

メッセージのちょうど真ん中の部分が指定した座標なります。
x=0.0のときは左端
(メッセージの左端がちょうど画面の左端に接します。)

x = 1.0のときは右端
(メッセージの右端がちょうど画面の右端に接します。)

値が増えるほど位置は右になります。
メッセージが改行(\n)されていても全体として左詰に表示されます。
-1.0 <= x < 0 メッセージの左端

指定した座標からメッセージが左詰で表示されます。
x = -1.0のときは右端
(メッセージの左端がちょうど画面の右端に接します。つまり、メッセージが完全に画面の右端に隠れます。)

値が減るほど位置は右になります。
メッセージが改行(\n)されていても全体として左詰に表示されます。
1.0 < x <= 2.0 メッセージの中心を通る部分

メッセージのちょうど真ん中の部分が指定した座標なります。
x = 2.0のときは右端
(メッセージの右端がちょうど画面の右端に接します。)

値が増えるほど位置は右になります。
メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。
-2.0 <= x < -1.0 メッセージの左端

指定した座標からメッセージが左詰で表示されます。
x = -2.0のときは右端
(メッセージの左端がちょうど画面の右端に接します。つまり、メッセージが完全に画面の右端に隠れます。)

値が減るほど位置は右になります。
メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。
それ以外 メッセージの中心を通る部分

メッセージのちょうど真ん中の部分が指定した座標なります。
ずっと画面の真ん中に表示されます。 メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。

引数yについて(縦方向)

yの範囲 指定したy座標を基準にするもの 値の増減と位置の関係
0.0 <= y <= 1.0 メッセージの中心を通る部分

メッセージのちょうど真ん中の部分が指定した座標なります。
y=0.0のときは上端
(メッセージの上端がちょうど画面の上端に接します。)

x = 1.0のときは下端
(メッセージの下端がちょうど画面の下端に接します。)

値が増えるほど位置は下になります。
-1.0 <= y < 0.0 メッセージの上端

指定した座標からメッセージが上詰で表示されます。
x = -1.0のときは下端
(メッセージの上端がちょうど画面の下端に接します。つまり、メッセージが完全に画面の下端に隠れます。)

値が減るほど位置は下になります。
それ以外 画面からはみ出て映りません。 画面からはみ出て映りません。

この表だとわかりにいので実際に実験して理解して下さい(^_^;)


引数holdTime

引数holdTimeに秒数をFixedPointValueで入力することで、その秒数だけメッセージを表示します。(ただし、フェードアウト・インなどにかかる時間は除きます。)ここに0.0を入力すると、このメッセージは(同じidのメッセージが表示されない限り)永遠に表示されます。


Script 1 ENTER
{
int monsters = ThingCount(0,999);//Tidが999のモンスターの数を数えます。
SetFont("BIGFONT");
hudmessage (s:"Destory \ch",d:monsters,s:" \cjmonsters!";HUDMSG_PLAIN, 0, CR_WHITE, 0.5, 0.4,4.0);
//文字の色を変えました。
}
//ちなみに結果は"Destroy ○○ monsters!"と表示されます。


Script 1 ENTER
{
hudmessage (s:"\cgmissions:\n \cj\rfind the secret key\n \cj\rescape from this facility";HUDMSG_TYPEON | HUDMSG_LOG, 0, CR_WHITE, -0.2, 0.4, 5.0, 0.1, 1.0);
//x座標が-0.2で左詰に表示します。
//ちなみに、\cjを書かなくても画面には同じように表示されますが、コンソールは色が変わって表示されます。
}


SetHudSize関数

ゲーム中に表示されるメッセージの一つ一つの文字は、ゲーム画面の解像度がどんな値であろうと一定の大きさで表示されます。
つまり、ゲーム画面が高解像でであれば相対的に文字が小さくなり、ゲーム画面が低解像度であれば、相対的に文字は大きくなります。
ここで、ゲーム画面の大きさによってメッセージの見え方が全く変わってしまうという問題が生じてしまいます。
メッセージを表示するだけならあまり問題ないのですが、HudMessage関数を使って画面上に表を作ったりしたときや、全画面が一つの画像で埋まるようにHudMessage関数を使ったりしたときなどに問題が発生します。

ここで、メッセージの一つ一つの文字を拡大・縮小することで、メッセージの見え方を一つにしてくれる関数がSetHudSize関数です。
SetHudSize関数の一般の形は

void SetHudSize(int width, int height, int statusbar);

まず、引数について紹介します。


引数widthとheight

基準となるゲーム画面の解像度を指定します。
widthに横幅を、heightに高さを入力します。
この解像度と異なったゲーム画面でプレイすると、自動的にメッセージを拡大・縮小してくれて、ゲーム画面とメッセージの大きさが相対的に同じになります。


引数statusbar

値が0であれば、ステータスバーを考えずにゲーム画面だけを考えて、拡大・縮小を行います。(ステータスバーの有無によってメッセージの位置が変わります。)
値が1であれば、ステータスバーがゲーム画面に占める領域も考えて拡大・縮小を行います。(ステータスバーの有無にかかわらずメッセージの位置はかわりません。)


SetHudSize(640,480,1);


さらに、SetHudSize関数を使用すると、HudMessage関数のx,y座標の入力の仕方が変わってきます。
例えばSetHudSize関数が先ほどの例のようになっているとき、画面の中心にメッセージを表示したいときは次の様にx,yを指定します。

hudmessage (text; int type, int id, int color, 320.0, 240.0, fixed holdTime, ...);

このように、FixedPointValueでSetHudSize関数で指定した縮尺のままで座標を指定しましょう。
ここで、文字の右詰、真ん中揃えなどの指定の方法が変わってきます。
小数第一位の値を変えることで文字のそろえ方を変えることができます。

引数xについて

xの小数第一位 指定した座標にくるメッセージの部分 改行されたメッセージの表示方法
.0 メッセージの中心 メッセージが改行(\n)されていても全体として左詰に表示されます。
.1 メッセージの左端 メッセージが改行(\n)されていても全体として左詰に表示されます。
.2 メッセージの右端 メッセージが改行(\n)されていても全体として左詰に表示されます。
.4 メッセージの中心 メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。
.5 メッセージの左端 メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。
.6 メッセージの右端 メッセージが改行(\n)されていればその行ごとについて全体として中央揃えになります。行ごとの中心のx座標は同じです。

引数yについて

yの小数第一位 指定した座標にくるメッセージの部分
.0 メッセージの中心
.1 メッセージの上端
.2 メッセージの下端

Script 1 ENTER
{
SetFont("smallfont");
SetHudSize(640,480,1);
hudmessage (s:"x:centers text and aligns right edge\ny:positions bottom edge of box";HUDMSG_PLAIN, 0, CR_WHITE, 320.6, 240.2, 0.0);
}
//x座標・・・それぞれの行が中央揃えかつ右詰
//y座標・・・下詰め


定数宣言

定数を使用すると、ACSの管理がとても楽になります。


複数にわたって同じ値を使用するとき、定数を使っておくと、値の変更があったとき、定数の値を変えるだけで全体の値を変えることができて非常に便利です。
宣言方法は次の様にします。


#define (定数名) (値または文字列)


#define PlayerTid 999
//定数PlayerTidの値が999と定義しました。

#define Message1 "This is a test"
//文字列を使うこともできます。

#define x 's'
//文字コードも使うこともできます。
//また、配列は使用できないようです。


定数は数字と同じように使えますが、代入はできません。
ちなみに、zdefs.acsにはACSを作るときに定義しなくても使うことのできる定数が宣言されています。
("TRUE"や"YES"や"T_IMP"などがそれです。)


定数の使用例

#define wait 70

Script 1 (void)
{
delay(wait);
Thig_Spawn(32,T_IMP,0,0);
delay(wait);
Thig_Spawn(32,T_REVENANT,0,0);
}
//この場合、delay関数の引数の値を変えたい場合、定数waitの値を変えるだけでよいので非常に便利です。


if文、switch文、ループ文

if文やswitch文やループ文を使用すると、より複雑なACSを作ることができます。
ここで論理演算や関係演算が重要になってきます。


if文

if文は、ある条件を入力しておいて、その条件が真(TRUE)か偽(FALSE)かでその後に行われるコマンドを分岐させることができます。
if文の書き方は


if(/*条件*/){
//TRUE時のコマンド
}
else{
//FALSE時のコマンド
}


条件が真(TRUE)なら"TRUE時のコマンド"を、偽(FALSE)なら"FALSE時のコマンド"(elseの下)を実行します。
また、条件が偽のときに行うコマンドが無ければ、else{//コマンド}の部分は書かなくても問題ありません。
また、コマンドが1行で終わってしまう場合は{,}をを使う必要はなく、そのままコマンドを書いても問題ありません。(後に出てくるswitch文などでもこれに当てはまります。特に断りが無い限り、コマンドが1行の場合、{,}は省略できます。)


Script 1 ENTER
{
int a = 3;

if(a == 3){
print(s:"a = 3");
//aが3ならこちらを表示
}
else{
print(s:"a != 3");
//aが3でないならこちらを表示
}

}


Script 1 (void)
{
int monsters1 = ThingCount(0,999);
//Tidが999番のモンスターの数を代入します。

int monsters2 = ThingCount(0,998);
//Tidが998番のモンスターの数を代入します。


//Tidが998番と999番のモンスターが全滅していれば下記の文章を表示します。
if(monsters1 == 0 && monsters2 == 0){
print(s:"You made it!");
}

//条件が"偽"だった場合のコマンドが無い場合は、書く必要はありません。

}


switch文

switch文を使うと、条件式の値に応じて色々なコマンドを実行することができます。
switch文の書き方は


switch(/*式*/){

case 値1:
//値1でのコマンド
break;

case 値2:
//値2でのコマンド
break;

//必要に応じて増やして下さい。

default:
//例外のコマンド
//"default"に限らず最後であれば"break;"は必要ありません。
}


"式"で入力した式の値が"値○"と同じになれば"値○でのコマンド"を実行し、switch文を終了します。
例えば、"式"が"値1"と同じになれば"値1でのコマンド"を実行します。
"式"と同じ値を持つ"値○"が無ければ、"default"があれば"例外のコマンド"を実行します。

if文を複数使うとswitch文と似たものを作ることができますが、かなりわかりにくくなってしまうのでswitch文を積極的に使いましょう。


//numberはint型の変数とします。
Script 1 ENTER
{
switch(number){
case 1:
print(s:"The \"number\" is 1");
break;

case 2:
print(s:"The \"number\" is 2");
break;

case 3:
print(s:"The \"number\" is 3");
break;

default:
print(s:"Error!");
//変数numberが1,2,3以外のとき
}
}


while文

while文はループ文です。同じ事や似たようなことを何回も実行させるときなどに便利です。
while文の書き方は


while(/*条件*/){
//ループ時のコマンド
}
//条件が真である限りループします


条件が偽ならループ文を無視して、ループせずにwhile文をスキップします。
真(TRUE)の場合、"ループ時のコマンド"を実行します。
"ループ時のコマンド"が完了したら、もう一度"条件"を調べて真(TRUE)ならもう一度"ループ時のコマンド"を実行します。
偽ならループ文を無視して、ループせずにwhile文をスキップします。
このように条件が真(TRUE)である限り、いつまでもループします。

この作業の流れは
条件を確認→(偽)脱出
↓(真)
ループ時のコマンド

条件を確認→(偽)脱出

ループ時のコマンド

(繰り返し)


Script 1 ENTER
{
int k = 1;
int sum = 0;

//kが100以下の間、ループします。
while(k <= 100){

sum += k;
//sum = sum + k;と同じです。

k++;
//kの値を1増やします。
}

print(d:sum);
}


このScriptは1から100までの和5050を表示させます。


Script 1 ENTER
{
int x = 1;

//xが4より大きくなるまでループします。
while(x <= 4){

Thing_Spawn(x,T_IMP,128,0);
//TidがxのThingにImpを召還します。

x++;
}
}
//Tidが1〜4のThingにImpを召還します。


このように、ループ文では

Thing_Spawn(1,T_IMP,128,0);
Thing_Spawn(2,T_IMP,128,0);
Thing_Spawn(3,T_IMP,128,0);
Thing_Spawn(4,T_IMP,128,0);

と書かなければならなかった手間を解消することができ、非常に便利です。
また、ループ文でしかできない事も多々あるので是非覚えておきましょう。

ただし、注意すべき事があります。下の例を見て下さい。


間違った例

Script 1 (void)
{
//TRUEはzdefs.acsで1と定義されています。
while(TRUE){
//"条件"はいつでも真(TRUE)なので、何回でもループします。

Thing_Spawn(100,T_IMP,0,0);

}
}


この様にループ文を作ると、同じ時間帯で永遠にループを行うのでZDOOMがゲームを強制終了させます。
delay関数を使うなどして時間をずらしたり、ループ文から脱出できるような構造にしましょう。

また、while文を使うと「特定のモンスターが倒されるまでScriptを止めておく」ということができます。
本来の使い方とは異なりますが、便利な方法なので紹介します。


while(ThingCount( /*モンスターの番号*/ , /*モンスターのTid*/ ))
delay(1);
//最小の待ち時間です。
//特定のモンスターが全滅するまで永遠にdelay関数(1tic待つ)を繰り返します。
//モンスターが存在すれば、条件に来る値は1以上になるので、真(TRUE)となります。


ここで、delay関数を使わないと同じ時間帯に永遠にループすることになってしまうので注意しましょう。

ちなみに、while文だけでなく、until文もありますが、while文との違いは、「ループするための条件が真が偽か」だけなので、このページでは特に説明しません。(until文は条件が偽ならループします。)


for文

for文はwhile文より自由度は低いと思いますが、非常に使いやすいです。
for文の書き方は


for(初期化文; 条件; 変更文){
//ループ時のコマンド
}


まず、初期化文でループを行う目安となる変数を宣言します。
条件はwhile文と同じで、条件が真(TRUE)ならループをします。
変更文は、"ループ時のコマンド"が一度終了する度に行われます。

この作業の流れは
初期化文を実行

条件を確認→(偽)脱出
↓(真)
ループ時のコマンド

変更文を実行

条件を確認→(偽)脱出

ループ時のコマンド

変更文を実行

・・・
(繰り返し)


例:while文の例をfor文で書き換えました。結果は同じです。

Script 1 ENTER
{
int sum = 0;

for(int k = 1; k <= 100 ; k++){
//変数kの宣言
//変数kが100以下ならループ文を実行
//ループ文が一巡すればkの値を1増やします。

sum += k;

}

print(d:sum);
//結果は先ほどと同じ5050です。
}


Script 1 ENTER
{
//変数angleが256になるまでループします。
for(int angle = 0; angle < 256; angle += 8){

Thing_ProjectileGravity (300, T_ARACHNOTRONPLASMA, angle, 16, 32);
//Arachnotronのプラズマ弾を角度angle(Byte Angle)の方向に上向きに発射します。

delay(2);
}
}
//プラズマ弾がぐるっと一周しながら発射されます。


for文のときは具体的な条件、変更文をあらかじめ決めておかなければならないので同じ時間帯に永遠にループなどは発生しにくいです。


ループ文の脱出、繰り返しなど

脱出

ループ文では条件を偽(FALSE)にしてループ文から脱出する方法以外にもループ文から脱出する方法があります。
ループ文の中に次の言葉を付け足しましょう。

break;

ループ文の途中で、breakと出会ったとき、そのループ文から強制的に脱出して、ループ文は終了します。


Script 1 (void)
{

//Tidが999のモンスターのつもりです。
while(ThingCount(0,999) > 0){

if(GameSkill () == SKILL_VERY_HARD)break;

delay(35*5);

Thing_Spawn(100,T_MEDKIT,0,0);

}
}
//GameSkill関数・・・現在のゲームのスキルに対応する値を返します。


このとき、スキルが"Nightmare!"であればこのループ(Tidが999のモンスターが残っている間、5秒に1回Medikitを出現させます。)から強制的に脱出します。


繰り返し

ループ文の最後まで進むと、ようやくもう一度ループしますが(条件が真の場合)、ループ文の途中でも強制的にループ文のはじめまで戻ることができます。
ループ中に次の言葉を付け足しましょう。

continue;

ループ文の途中でcontinueに出会ったとき、そのループのはじめの部分から実行し直します。
ループをループ文の途中で起こそうとするときに便利です。


仕組み

while(/*条件1*/){

//内容1

if(/*条件2*/)continue;
/*ここでif文を使わないとcontinueが必要でなくなるので(ループ文の最後みたいになってしまいします。)、if文を使っています。
専らif文とセットで出てくると思います。*/

//内容2

}


上の「仕組み」を説明すると、
・条件1が真であるとします。
・内容1が実行されます。
・条件2が真であるときは、continueが実行され、whileの最初まで戻ります。偽なら、そのまま内容2を実行します。
・先ほどの条件2が真だった場合、条件1が真であるならばループ文の最初に戻り、内容1が実行されます。条件1が偽だったらループ文は中止されます。
・条件2について調べ、真ならまた戻り、偽なら内容2を実行します。

流れとしては

条件1→偽:ループ文から脱出

真:内容1を実行

条件2→偽:内容2を実行し、通常通りループ

真:すぐさまループし、条件1が真ならループ、偽ならループから脱出


do whileループ

while文では、「ループするための条件を一度だけ無視して、ループ文を実行する。」ということができます。

書き方は


do{

//内容

}while(/*条件*/);


このとき、while文の中に入ったとき、"条件"が真であれ偽であれ一度、"内容"を実行して、実行し終わってから"条件"を確認してもう一度ループするか決めることができます。


restartによるループ

restartを使うと、簡単にループを作ることができます。


書き方(一例)

Script 1 (void)
{
//内容

restart;
//ループします。
}


上の例で説明すると、restartに出会ったとき、実行しているScriptを最初から実行し直します。
簡単にループを作ることができるので便利です。


terminateによる脱出

terminateを使うと、実行しているScriptから脱出することができます。


書き方(一例)

Script 1 (void)
{
//内容

terminate;
//このScriptが終了します。
}
//ちなみに、terminateもif文と組み合わせないと、あまり意味が無いような気がします。


上の例で説明すると、terminateと出会ったとき、実行しているScriptからその場で脱出(終了)します。


スコープ

変数を定義する場所、方法によって定義した変数を使用できる範囲(スコープ)が決まってきます。
スコープを上手く使うと、ACSの管理が楽になるので是非覚えましょう。

Scopeの名前 変数の使用できる範囲 定義する場所
Script Scope 変数が定義されたScriptやfunction内でしか使用できません。 Scriptやfunctionの中
Map Scope 取り扱っているMAPが同じACSで使用できます。 Scriptやfunctionの外
World Scope すべてのMAPやfunction内などで共通して使用できます。 Scriptやfunctionの外
Global Scope すべてのMAPやfunction内などで共通して使用できます。 Scriptやfunctionの外

宣言方法


Script Scope
Script(またはfunction)の括弧内で変数宣言するとScript Scopeになります。
例えば

Script 1 (void){
int a = 1;
}

この時、変数aはScript Scopeになります。
また、この変数aはScript 1内でしか使用できません。
不便な点が多いと思われますが、次の様な事をするときなどに便利です。


Script 1 (void){
int a = 1;
//内容
}

Script 2 (void){
int a = 2;
//内容
}


この場合、2つの変数aのスコープはScriptScopeなので、お互いに影響し合うことはなく、独立して使用することができます。


Map Scope

Scriptの括弧外(またはそのACSが使用するLibraryのScriptの括弧外)で変数宣言するとMap Scopeになります。
例えば

int a = 0;

Script 1 (void){
int b = 1;
}

この時変数aはMap Scopeですが変数bはScript Scopeです。
また、この変数aはこのMAPで使用するScriptすべてに使用できますが、
変数bはScript 1内でしか使用できません。


World Scope

次のように変数を宣言するとWorld Scopeになります。

world int 番号:変数名;


例えば

world int 1:x;

この宣言をすると1番のWorld Scopeを持った変数をxと宣言します。
また、変数の名前が違っても他のMAPにおいて(番号)が共通であれば同じ値になります。
また、WorldScopeで変数がlibraryに宣言されているとします。そのlibraryを使用しているACSはその変数を宣言せずに使用することができます。

例えばMAP01のScriptでは

world int 1:x;

Script 1 ENTER
{
x = 10;
}

とし、MAP02のScriptでは

world int 1:y;

とすると、MAP01をクリアしてからMAP02に進むと変数yの値が10になります。
また、MAP02から始めると変数yの値は0になります。
ちなみに、hubで繋がっているMAP同士で値を共有するのだと思うのですが、私の場合、GZDOOMで試してみましたが、hubで繋がっていないMAP同士でも値を共有してしまいました・・・


また、MAP01のScriptでは

world int 1:x;

Script 1 ENTER
{
x = 10;
}

とし、MAP02のScriptでは

world int 2:x;

とすると、MAP01をクリアしてからMAP02に進んでも共通の変数ではないのでxの値は引き継がれません。


Global Scope

次のように変数を宣言するとGlobal Scopeになります。

global int 番号:変数名;

これはWorld Scopeと似ていますが、World ScopeとGlobal Scope番号を同じにしても共通にはなりません。
また、これはすべてのMAPやfunctionの中やlibraryの中でも共通して使用できますが、WorldScopeも使用できてしまうので、違いがよくわかりません・・・


※注意点

次の様なScriptがあるとします。

Script 1 (void)
{
int a;
//Script Scope
}

Script 2 (void)
{
int a;
//Script Scope
}

この場合、正しく動作しますが

int a;
//Map Scope

Script 1 (void)
{
int a;
//Script Scope
}

の場合は、正しく動作しません。


関数の作成

今まではZDOOMが既に用意していた関数を使ってACSを作ってきましたが、これらの関数や演算子を自由に組み合わせて独自の関数を作ることができます。

関数の宣言方法は


function type function_name(type arg1 , type arg2 , ... , typen)
{

//内容

return value;
}


functionはこれが関数であるという宣言です。
typeでこの関数の戻り値のデータ型(intなど)を指定します。
function_nameで関数の名前を決めます。名前には空白は使用できません。
([type arg1 [, type arg2 [...]]])は引数です。必要に応じて増やして下さい。もし引数が必要ないなら、voidと入力して下さい。
returnはこの関数の戻り値を意味します。関数がreturnと出会ったとき、この関数は終了してreturnの後に書いている値(valueの部分、変数でも可)を戻り値とします。

※注意1:関数を定義すると、定義した部分より後のACSファイルの部分のScriptでその関数を使用することができます。
つまり、その関数を使用するACSファイルの先頭に定義することで解決できますが、不便なことが多いので、libraryの中で関数を定義しておく方が便利だと思います。

※注意2:この関数内で宣言された変数はScriptScope扱いです。ScriptScopeより範囲の大きい変数と同じ名前の変数を宣言するとにすると、エラーが発生します。


ZDOOMには搭載されていない関数

次に、関数の作成の章の具体例も兼ねて、ZDOOMには搭載されていなくて、あったら便利な関数を少しだけ紹介します。(ZDOOMWikiより)


例1:関数abs

//絶対値を求める関数です
function int abs (int x){
if (x < 0)
return -x;

return x;
}


function int abs (int x)
で関数absの戻り値はint型の整数であり、引数xがあると宣言します。

if (x < 0)
return -x;
では引数xがマイナスの場合はプラスに変えて戻り値とし、この関数を終了します。

return x;
引数xがマイナスでないなら(0以上なら)そのままの値を戻り値とします。

では、実際に関数absを使用してみましょう。


使用例

//関数absの定義
function int abs (int x){
if (x < 0)
return -x;

return x;
}

Script 1 ENTER
{
int a = -1;
int b = 3;
print(s:"abs(a) = ",d:abs(a),s:"\nabs(b) = ",d:abs(b));
}


このACSを実行してみて、"abs(a) = 1 abs(b) = 3"と表示されれば成功です。


例2:関数pow

//累乗の関数です。
function int pow (int x, int n)
{
if (n < 1)
return 1;

//この変数yはScriptScopeです。
int y = x;

//nから1を引いてから条件を確認します。
while (--n)
y *= x;

return y;
}


例3:関数sqrt

function int sqrt (int x){

int r;

x = x + 1 >> 1;

while (x > r)
x -= r++;
//x = y^2(yは0以上の整数)とすると
//1 + 2 + ・・・ + r = r * (r + 1) / 2より
//r^2 + r - y^2 - 1 >= 0を満たす最小の0以上の整数r(=y)を求めています。

return r;
}


また、ここでは紹介しませんが、function内でBuilt-in ACS functions以外にもAction specialsを使用することができます。

注意点:function内ではdelay関数が使用できません。意外と不便です(^_^;)(色々抜け道はありますが・・・)


具体例

「ACSの構造の解説」で解説したすべてを踏まえて(と言っても一部ですが・・・)最後に具体例を紹介しておきます。


配列変数の例1

str numbers[4] = {"One" , "Two" , "Three" , "Four"};
int y = 3;

Script 1 (void)
{
While( y >= 0){

print(s:numbers[y]);

delay(35);

y--;
}
print(s:"fight!");
}


このScriptでは"Four"〜"One"までカウントダウンをしてから画面に"fight!"と表示されます。


配列変数の例2(補足を先に読んで下さい)

int end = 0;
int x[9];
//この時点ですべての要素に0が代入されています。


//パズルのScriptです。
//引数があることに注意して下さい。
//この引数はプレイヤーが踏んだSectorとそれぞれ対応しています。

Script 1 (int tag)
{

if(x[tag] == 0){
Sector_SetFade(1 + tag, 128, 0, 0);//プレイヤーが踏んだSectorを赤くします。
x[tag] = 1;
}

//9個のSectorが十字型に赤くなっているときは以下の様な条件の状態になっています。
if(x[0] == 0 && x[1] == 1 && x[2] == 0 && x[3] == 1 && x[4] == 1 && x[5] == 1 && x[6] == 0 && x[7] == 1 && x[8] == 0){

Door_Open(10, 16);
//ゴールのドアが開きます。
print(s:"Good job");

Thing_Remove(1);
//Actor hits floorはもう必要ないので消去します。
end++;
//変数endの値を(結果的に)1にします。
}
}

Script 2 (void)//パズルをリセットします。
{
//変数endの値を確認します。(1であればもうパズルは解かれているのでリセットしません)
if(end == 0){
Sector_SetFade(1, 0, 0, 0);
Sector_SetFade(2, 0, 0, 0);
Sector_SetFade(3, 0, 0, 0);
Sector_SetFade(4, 0, 0, 0);
Sector_SetFade(5, 0, 0, 0);
Sector_SetFade(6, 0, 0, 0);
Sector_SetFade(7, 0, 0, 0);
Sector_SetFade(8, 0, 0, 0);
Sector_SetFade(9, 0, 0, 0);
x[0] = 0;
x[1] = 0;
x[2] = 0;
x[3] = 0;
x[4] = 0;
x[5] = 0;
x[6] = 0;
x[7] = 0;
x[8] = 0;
}
}


3*3の9個のマスがあり、マスを踏むと、踏んだマスが赤くなります。十字型に9個のマスを踏むとドアが開く仕組みです。
こちらを比較しながら参考にして下さい。

補足:主に関数やThingの説明です。

関数Sector_SetFade (tag, r, g, b)

効果
指定したSectorの「遠くを見るほど混ざる色(デフォルトは黒なので、遠くは暗く見えます。)」を変更します。
例えば、赤色に設定すれば、そのSectorが全体的に赤っぽくなります。

引数tag
色を変えたいSectorをLineDefTagで指定します。

引数r,g,b
それぞれ赤色、緑色、青色(光の三原色)の度合いを指定します。この混ざり具合で色が決まります。0が最小で255が最大です。

関数Thing_Remove (tid)

効果
指定したThingがゲームから消えます。今回はActor hits floorを消すために使用しています。

引数tid
消したいThingのTidを指定します。

Map上にあるThing : Actor hits floor

効果
このThingがあるSectorの床をプレイヤーが踏むと、Actor hits floorであらかじめ指定されているSpecialが実行されます。
今回はSpecialにACS_Execute関数と内容が同じ関数を使用しています。Thingごとによって引数が違うことに注意して下さい。


このページで紹介した関数の詳しい紹介はSpecial&関数集を見て下さい。


これで、ACSの構造の解説は終了です。
次に、Libraryの使用へ進んで下さい。


Home Back