QuakeC入門

3.覚えておくとよいこと

 これまでの章で「わかんねーよ」と思ったことが下記のリストに見つかったらくりっくしてね!

1.void、float、vector、string、entity?何これ?
2.関数ってなんじゃ?
3.QCのソース上でどこからも呼び出されていない関数や、定義内容の見当たらない関数が存在する
4.サウンドやモデルのファイルネーム、何でお前は知っとるんじゃ?ナメんな!
5.selfだのotherだのあちこちで見るぞ!なんやっちゅーねん!



1.void、float、vector、string、entity?何これ?

 プログラムを組む上で欠かせないのが様々な処理を行うためにデータを格納しておくための変数でしょう。
 Quake-Cには全部で5種類の変数及び変数の集合体となったオブジェクトが用意されています。 普通、C言語では新たなオブジェクトをユーザー側で定義できたりするんですがQuake-Cではできまへん!でも、その方が簡単でいいや!気にしない気にしない。 変数だろうとオブジェクトだろうとQuake-Cじゃみんなデータを格納するための箱って考えればOK! 「コレ使うよー」と宣言して、それにデータを代入するだけのこと、難しく考える必要なーし!
 ではその5種類について簡単に説明しますです。

void タイプ
 voidは何も格納することができない変数タイプです。「ならいらねーぢゃん」といいたいところですが、これは関数定義の際「この関数は返し値がないよー」ということを示すために関数定義文の先頭へくっつけます(関数定義については「2.関数ってなんじゃ?」で説明するニョロ)。 しかし、無理矢理それ意外の目的に使うことはできます。次のfloatタイプの項を見てもらえばわかりますが、新たな変数やオブジェクトを使いたい時は
	変数タイプ 変数名1,変数名2.....;
または
	オブジェクトタイプ オブジェクト名1,オブジェクト2.....;
のように一度宣言を行わなければなりません(各宣言の最後にセミコロン「;」を付けるの忘れないで)。voidタイプも一応上記のような新規voidの宣言だけは行えるようです。例えばvoidタイプのtarouとhanako(定番の発想やね)を宣言したい時は

	void tarou,hanako;
とすればOKなんですけど、「宣言できるだけ」で後は値を代入することも引き出すこともできないので何の意味もありません。 やるだけムダです...っていうか絶対やるな!ぎゃふん!

float タイプ
 floatタイプは符号付き浮動小数点数...早い話が+でも−でも、小数点付きでも格納できる変数タイプ。 タダ単に数値を格納できる変数タイプだと思えば楽勝。 普通のC言語では扱う数値の使用目的に応じて変数のタイプを色々選ぶんですけど、Quake-Cでは数値を扱えるのはfloatしか用意されてない! ないからコレを使うしかないのよアンタ!
 新規floatタイプ変数宣言の例を以下に記します。内容はfloatタイプのugegeとuganを宣言してugegeに0.2を代入する!
	float ugege,ugan;

	ugege = 0.2;
簡単れすね!宣言と同時に値を代入できちゃったりもします。例えばugegeに-120、uganに3.14を最初から代入した状態にしたいなら
	float ugege=-120,ugan=3.14;
こんだけ!同じタイプの変数やオブジェクトを宣言する場合は変数及びオブジェクト名をカンマで区切ればいくらでも一度に宣言できちゃう! だがしかし、100個近くの変数を一度にカンマで区切りまくって宣言できるかどうかは試したことがないのでぼくちゃんは知らない(やらねーよフツー)。 で、更にugegeとuganの値を足してmuhyoに代入したいならこんな風に
	float ugege=5,ugan=-2.5,muhyo;

	muhyo = ugege + ugan ;
この結果、muhyoには2.5が代入されます。余談ですが、なるべく演算子(+-*/等)の両端はスペースを入れるよう心がけた方がよいです。 時々コンパイラ側の解釈によってエラーにされてしまうかもしれません。

vector タイプ
 vectorタイプは3つのfloatタイプの数値を持つ変数です。主に(X,Y,Z)の3次元座標を格納するために使用する物ですが、 そうでない場合もあります。単に「3つの値を一度に受け渡しできる便利な変数」と考えた方がいいかもしれません。 例えば新規のvectorタイプのhanageを宣言後、0.2、-2.3、0.75の3つの値をhanageへ代入したい場合、以下のように記します。
	vector hanage;

	hanage = '0.2 -2.3 0.75' ;
3つの値をスペースで区切り、それをシングルクォーテーション(ダブルクォーテーションはダメ)で囲んだ物をhanageの右辺へ置けばhanageへ3つの値が代入されます。 もちろんfloat同様、宣言時に値を代入しちゃってもいいです。vector同士は足し算、引き算が可能で
	vector hanage = '0.2 -2.3 0.75',hige = '1.2 3 -1.5' ;

	hanage = hanage + hige ;
こう記述すればhanageへは元のhanageの値とhigeの値を足した'1.4 0.7 -0.75'の値が代入されます。 なお、vector同士での掛け算や割り算はできません。 更にこんなこともできます。
	vector hanage = '0.2 -2.3 0.75' ;

	hanage = hanage * 2 ;
この結果、hanageの値は'0.4 -4.6 1.5'になります。 そう、元のhanageに入っていた3つの値がそれぞれ2倍されてhanageへ代入されました。 このようにvectorタイプへ単一の数値を掛ける、またはvectorタイプを単一の数値で割った場合、vectorタイプの値3つがそれぞれ影響を受け、結果はvectorタイプとして弾き出されます。 しかし、この組み合わせでの足し算と引き算はできません。
 vectorタイプに含まれた3つの値を別々に参照したい場合はどうしたらよいのかというと、 vectorタイプのhanageを例に挙げると、hanage_x、hanage_y、hanage_zというように、 vectorタイプの変数名へそれぞれ「_x」「_y」「_z]を付加するだけで通常のfloatタイプ変数として扱えます。  vectorタイプの性質、御理解いただけたでしょうか?

string タイプ
 文字列を扱うタイプの変数です。stringタイプの変数ponkoとdonbeを宣言して適当な文字列を代入するという例を示すと
	string ponko,donbe ;

	ponko = "maps/e1m1.bsp" ;
	donbe = "Hara Hetta Yo-!\n" ;
stringタイプは主にファイルネームの指定やプレイヤーへのメッセージ表示等の必要とされる関数を実行する際の引数に用いられます。 代入する文字列はダブルクォーテーションで囲ってください。
 上の例でdonbeの最後に「\n」とあるのは何かと疑問に思われたでしょう。 これは改行コードです。プレイヤーへのメッセージ等表示の際、 メッセージの終わりに改行コードが含まれていないと次回メッセージを表示した時に、前回メッセージの終わり位置に続けてメッセージが表示されてしまいます。 改行コードは表示の開始位置を新しい行の先頭へリフレッシュするための物なのです。

entity タイプ
 これを知らずしてQuake-Cなんて使えまへん! entityタイプを簡単に説明するなら「Quakeの世界に存在するもの全てがそれぞれ一つずつ持っている、彼らの固有の情報を格納することのできる変数の集合体」といったところでしょうか。 動き回るモンスターやプレイヤー、弾丸だけではなく、扉、ボタン、動く床、マップ自体までもが個別にentity情報を持っているのです。
 プレイヤーのentity情報を例に挙げると、プレイヤーの位置、移動速度、体力、向き、その他いっぱいの情報がfloat、vector、string、entityのタイプで格納されていますが、 よほどマニアックなことをやろうと思わなければ必要なentity情報は限られています。 entityオブジェクト下のentity情報が格納された変数のことをメンバ変数とでも呼びましょうか。 ここでは、カレントentity、self(カレントentityのselfについてはこの後で詳しく説明)から新しく宣言したentityタイプhogyanへ情報をコピー、そしてhogyanからvectorタイプのbafunへ位置座標の情報を取り出す例を記します。
	entity hogyan ;
	vector bafun ;

	hogyan = self ;
	bafun = hogyan.origin ;
ここで疑問に思うのはhogyan.originという表記でしょう。これはentityの位置座標を格納しているメンバ変数origin(vectorタイプ)の参照を意味します。 entity下のメンバ変数参照はentity名とメンバ変数をピリオドで繋げることで可能になるのです。 別の例でhogyan下のhealth(floatタイプの体力値)を参照したいならhogyan.healthという感じ、1章のワンちゃん体力モリモリの項を見るとこの例がありますね。
 さらに別の例で、hogyan下のenemyはhogyanの敵であるentityの情報を格納しているentityタイプですが(こんがらがらないように良く考えてください)、このenemyの位置座標を参照したい時は一体どうしたらいいのでしょうか? 答えはhogyan.enemy.originです。さらにさらに、hogyanの敵の敵の体力を知りたい場合はhogyan.enemy.enemy.healthという表記になります。 まあ、entityのメンバ変数参照はピリオドで区切るとだけ覚えていればよろしおます。
 注意したいのは「新しいentityタイプを宣言しただけでは新規の敵やミサイルを作ったことにはならない」ということです。entityタイプは「entity情報を格納することのできる箱」でしかないのです。 新規のentityを作り、そのentity情報へ設定や参照を行うには、spawnという関数を使用してQuakeのentityリストへ新たなentityを加えてもらい、 その結果返されるentity情報をこちらで宣言したentityタイプのオブジェクトへ受け取らなければならないのです。以下に新たに宣言したentityタイプmuhyoに新規entityの情報を受けとるという簡単な例を示します。 spawn関数については後程「ビルトイン関数」関連の章で説明するのであまり深く考えなくてもよいです。
	entity muhyo ;
	muhyo = spawn() ;



2.関数ってなんじゃ?

 関数は1つのプログラムを組み立てるためのパーツだと考えれば分かりやすいかもしれません。各関数は後で出てくるmodelのアニメーションは例外として普通
	返し値のタイプ(引数) 関数名 =
	{
		・
		・
	関数の処理内容
		・
		・
	};
の構成になっています。
 試しに処理内容が空っぽの返し値無し、引数無しの関数Hana_mogeraを作ったとしたら
	void() Hana_mogera = 
	{
	};
または、もっと省略したいなら
	void() Hana_mogera ;
こうなります。更に、この関数Hana_mogeraを別の返し値無し、引数無しの関数Test_dayon中で呼び出すとすると関数Test_dayon
	void() Test_dayon = 
	{
		Hana_mogera ();
	};
上記のように記述します。一つ前の項目で説明したvoidが出てきていますね。
 なんにもしない関数というのもチョットさみしいので「指定した2つの数を足して答えを返す」という処理を関数Hana_mogeraに記述して、 関数Test_dayonから呼び出してみます。
	float( float baka , float aho ) Hana_mogera = 
	{
		local float kotae ;

		kotae = baka + aho ;

		return kotae ;
	};

	void() Test_dayon = 
	{
		local float unko,kuso = 56,kasu = 44 ;

		unko = Hana_mogera ( kuso , kasu ) ;
	};
  この結果、関数Test_dayonが実行されると変数unkoには56と44の和、100が代入されます。 関数Test_dayonは関数Hana_mogeraへkusoとkasuという2つの引数を渡し、その実行結果をunkoへ受け取っているわけですが、 関数Hana_mogeraにおいて「返し値の変数タイプはfloatタイプ」「引数が2つのfloatタイプ」「変数kotaeを返し値にする」 等の表記を行っていることが理解いただけるでしょうか?
 さて、上記のサンプルリスト内の変数宣言の前に付加されているlocalとは何でしょうか? これはローカル変数の宣言で、宣言した変数が宣言を行った関数内のみで有効という意味です。 よって宣言を行った関数以外の関数からの参照はできず、宣言を行った関数内の処理が終了すると共にローカル変数は消滅してしまいます。 localの表記を取り去って変数を宣言するとそれはグローバル変数の宣言となり、他の関数からも参照可能となりますが、 グローバル変数は各関数内で宣言するべき物ではありません。今のところは変数宣言にはlocalを付けるよう心がけた方が良いでしょう。

 上記サンプルリストは関数Hana_mogera、関数Test_dayonの順に並んでいますが、これには意味があります。 もしもTest_dayonHana_mogeraの順に表記すると、コンパイラが関数Test_dayonの定義を参照した際、 関数Test_dayon内に関数Hana_mogeraの呼び出しを見つけた時点ではまだ関数Hana_mogeraの定義が行われていないため、 コンパイラは「Hana_mogeraは存在しない関数」と判断し、この表記をエラーとしてしまうからです。
 Test_dayonHana_mogeraの順に表記したい場合は以下のようにしてください、
	float( float a , float b ) Hana_mogera ;

	void() Test_dayon = 
	{
		local float unko,kuso = 56,kasu = 44 ;

		unko = Hana_mogera ( kuso , kasu ) ;
	};

	float( float baka , float aho ) Hana_mogera = 
	{
		local float kotae ;

		kotae = baka + aho ;

		return kotae ;
	};
一番上に空っぽの関数Hana_mogeraがあります。これはプロトタイプの宣言といって、 この偽の宣言によってコンパイラが関数Test_dayon内を参照した際「Hana_mogeraは存在する関数」と判断してくれるようになり、 後方にある本当の関数Hana_mogeraは偽の宣言を定義しなおす形になります(関数の上書き)。 簡単にいえばコンパイラをだまくらかすんですな。
 プロトタイプの宣言は呼び出す関数と呼び出される関数が同一のソースファイル上にある場合はあまり使用しませんが、 別々のソースファイルに分かれている場合等は役に立ちます。 各ソースファイルのコンパイル順序についてはprogs.srcに記載されたファイルリストを見れば分かります。



3.QCのソース上でどこからも呼び出されていない関数や、定義内容の見当たらない関数が存在する

 Quake-Cのソースを眺めていると「この関数はどんな目的で使われてんだ?」 てな感じで各関数が実際に呼び出されている個所を探したくなるものですが、どこから呼び出されているのか全くもってわからん関数があります。 モンスターの初期設定関数(体力やら各イベントに対応する関数名を指定している)がそれにあたり、 試しに2章でも扱ったザコキャラGRUNTに関して記述されているソースファイルsoldier.qcを開いてソース末尾にあるGRUNTの初期設定関数monster_armyを見てみましょう。 この関数の呼び出し元は一体Quake-Cソースのどこに存在するんでしょうか?ないんですな、これが。
 Quakeの各面スタート時、Quakeのシステムはマップデータ(bspファイル)からの情報を元にモンスター達をマップ上に置いていきます。 この時bspファイルから得られた情報の中に各モンスターの初期設定関数名が含まれており、 システムはentityを作成後、詳細設定のためにこの関数を呼び出しているらしいのです。 だからQuake-Cのソース上にモンスターの初期設定関数を呼び出している個所が見当たらなかったんですね。
 さらにこのことから言えることは、全く新しいモンスターをQuakeへ追加したいなんて場合はマップデータにも手を加えなければならないっちゅーことになりますな! 「そんな面倒なことはゴメンだ!」という方は既存のモンスターを新規モンスターに置き換えるしかありません。

 反対に、「あちこちで呼び出し個所が見られるのに、肝心の定義場所が見つからない」という関数もあります。 それらはビルトイン関数という物で、Quakeのシステムがあらかじめ用意している関数です。 通常の我々がQuake-Cで定義した関数の処理はQCコンパイラでコンパイルされたものをゲーム実行時にQuakeのシステムが解読しながら実行している(インタープリタ方式)のに対して、 ビルトイン関数はQuakeの実行ファイル中に含まれているルーチンを直接コールして実行するため、前者よりも高速な処理が期待できます(つっても今のパソコンって速いから関係ないくさ)。
 ビルトイン関数の定義場所は正確に言えばソース上に存在し、defs.qcというソースファイルでそれらしい場所が確認できますが、 関数の定義内容は「#番号」という奇妙な物ばかりです。 恐らく「#」に続く番号を元にQuakeのシステム上に用意されたジャンプテーブルから各内部処理に分岐しているのでしょうが、 触らぬ神に祟り無し、ビルトイン関数の名前を参照するだけでdefs.qcのこの内容には変更を加えないようにしましょう。本当に触っちゃダメ!ゲームがクラッシュするでぇ!
 ちなみにビルトイン関数を我々が再定義(上書き行為)することはできません。コンパイル時にエラーとなります。



4.サウンドやモデルのファイルネーム、何でお前は知っとるんじゃ?ナメんな!

 ソース上で度々目にする以下のような表記
	setmodel (self, "progs/soldier.mdl");
だとか
	precache_file ("gfx/help0.lmp");
だとか
	sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM);
だとか...
 上記において指定されている様々なファイルネーム。「こんな名前のファイルなんかQuakeのディレクトリ内どこを探しても見つからないじゃないか!」 と怒ってしまった方はいないでしょうか?
 Quakeのインストールされているディレクトリ内には\id1というディレクトリが存在します。 その中にpak0.pakpak1.pakという2つのファイルが有るでしょ? これはQuakeの各データファイル群をまとめた圧縮ファイルなんです。それを解凍すれば、アラ不思議、\id1 のディレクトリ下には\gfx\maps\progs\soundなんてディレクトりが出現するんですな。Quake-Cのソース中でファイルネームを指定する場合、
	ディレクトリ名/ファイル名
という形で指定します。サウンドファイルのみ、 \sound下のディレクトリ名とその中のファイル名という組み合わせで指定するようです。

 さて、そこでどうしたら解凍できるのか?ということになります。私の愛用しているツールを紹介しますです。

Winpakのダウンロード
Title File Name Size Site
Winpak 1.3 wpak1332.zip 151KB ftp://cdrom.com/pub/quake/utils/bsp_pak_tools/






5.selfだのotherだのあちこちで見るぞ!なんやっちゅーねん!

 Quake-Cのソースを眺めているとselfotherというentityタイプのオブジェクトが頻繁に見られます。 これについてちょいと説明するでやんす。
 selfカレントentityと呼ばれ、関数を呼び出しているentityの情報を格納しています。 といってもピンと来ませんね。次のように考えると分かりやすいかもしれません。
 Quakeの戦場には多数のモンスターが歩いたり、走ったり、怒ったり、攻撃したりしています。 これらの動作を行うためには当然、それに対応した関数を呼び出す必要があり、 各モンスターは自分の動作に必要な関数をシステムに呼び出してもらうために順番待ちをしています。 自分の順番が回ってきたらシステムに自分のentity情報をselfへコピーしてもらい、関数を呼び出してもらうのです。
 ですから各関数でselfの内容を変更すれば呼び出し元のentityの状態を変化させることができるんです。

 次はotherについて。これを利用する機会は結構限られています。 簡単に言うと「otherselfの接触した相手」といったところでしょうか。
 例えば一個のロケットが飛んでいるとします。当然目的は「ぶつかった相手をブッ殺す」ためであり、 こういった類のentityはすべて発射元のentityが発射時に「何かに接触した際、呼び出す関数名」(仮に接触関数とでも呼びましょうか)を指定しているものです。 ですからロケットが何かにぶつかると指定の接触関数が呼び出されます。ぶつかったからには相手の体力を奪わねばなりません。 ここで登場するのがotherなのです。
 接触関数が呼び出される際、selfにはロケットのentity情報が、 otherにはぶつかった相手の情報が入ります。 ですからあとは接触関数内の処理でotherの体力値減らしを行えばOK!
 otherは接触した相手、 otherは接触した相手、otherは接触...と覚えてください。

 まあ、これに関しては2章の「GRUNTを出世させよう」のロケット処理を見てもらうとてっとりばやいです。


e-mail:ponpoko@axcx.com