第6回 カメラワーク
3D アクションゲームでは、ゲームの演出にで、その機能を追加できて便利です。

いろいろな視点が利用されています。今回は、J3Wを使って「空間の見せ方」を実験してみま

画面 1 は、lib/xsolid.j3c を使った 8 種類の形状を作成するプログラムの実行例です。付■付属ライブラリの使用法

録 CD-ROM にソースと実行ファイル（t e s tsolid.j3c、testsolid.j3d）がありますから、す。J3Wでは、三次元空間に存在する物体がすべて視点になることができます。視点の「移動」

J3W付属のサンプルプログラムが共用するラや「切り替え」を実際に試してみましょう。

イブラリは「j3w-643/j3c_script/lib」以下

これまでは、サンプルプログラムのソースコーのディレクトリにあります（表1）。表1のファドのリストをすべて掲載していましたが、サンイルをインポートして、クラスならば、「new(プルが長くなってきたので、J3Wに付属していクラス名 , データサイズ)」を実行すると機能

J3Wでは、See()組み込み関数を実行したイるコードを利用することにします。共用できるが追加できます。継承してクラスの機能を拡張

ンスタンスから見ている空間が、画面に表示さ機能をまとめたファイルを用意しておくと、プすることもできます。ライブラリの場合は「ラれます。実体化している（NewObjectを実行す

ログラムを作成するときにインポートするだけ

イブラリ名 . 定数名」または「ライブラリ名 .

るか、 child で生成された）インスタンスは、メソッド名」
で、どのクラスからでもライブラすべて視点となることができます。視線は、物リに登録されている定数やメソッドが利用でき体の原点からX軸方向（前方）に固定されます。

ます。

視点を持つ物体をここでは「カメラ」と呼ぶこ【表 1】J3C 付属ライブラリ説明

実際に実行してみてください。

■カメラワーク

ファイル

種別

color.j3c

library 色名の定義

ユーザーのホームディレクトリが/home/jun/とにします。カメラは自由に移動したり、向きkey.j3clibrary キーコード名定義

で、ここに J3W を展開した場合、付属ライブを変えることができますが、「歩きながらあた

ssolid.j3clibrary 単純な形状を生成

ラリの eye.j3c をインポートするにはリストりを見回す」というような人間の目と同じよう

zsolid.j3clibrary Z 軸方向に伸びる形状生成

1のように絶対パスで指定します。また、/home/な動きをさせるには、ちょっとした工夫が必要

xsolid.j3clibrary X 軸方向に伸びる形状生成

jun/j3w-643/j3c_script/work/ のように、になります。物体に階層構造で子供となる物体

empty.j3c

class

何もしない空のクラス

作業ディレクトリを J3W を展開したディレク（カメラ）を作っておくと（図 1）、視点の運動

eye.j3cclass

簡単な視点クラス

トリに置いた場合、付属ライブラリをインポーに自由度を持たせることができます。アームの先に付いたカメラが、物体の動作とは独立して色々な方向を見ることができるようになります。

esckey.j3c

class

ESC キー終了チェッククラス

トするには次のように相対パスで指定できます

revolutn.j3cclass

回転体生成クラス（本誌付録CD-ROM収録のサンプルプログラム

fps̲pos.j3c

class表示速度計算クラス

もこの指定方法になっています）。

import "../lib/eye.j3c"【リスト 1】付属ライブラリ eye.j3c のインポート

3D アクションゲームでは「見せ方」をいろいろ工夫しています。Quake タイプの一人称視点の3Dアクションシューティングとか、格闘ゲームのような第3者の視点、または自キャ

import "/home/jun/j3w-643/j3c_script/lib/eye.j3c"

ラ後方からの視点、などがあります。ボクシングの場合を考えると、選手（自分）の視点、【画面 1】testsolid.j3c の実行例

【図 1】カメラとアーム相手選手の視点、選手がよく見えるように移動しているレフリーの視点、離れたところで静止して全体を見ている観客の視点、などが思いつきます。
今回は、「視点の切り替え」、「視点の移動」、といったカメラワークに関するサンプルを詳細に見ていきましょう。以下では、プログラムをいくつかの部分に分けて解説しますが、プログラ
ム全体は付録 C D - R O M にファイル名

c a m e r a w k . j 3 c として収録しています。

2002.2

Linux Japan49

camerawk.j3cでは、5つの付属ライブラリを使用しています。ここでは、J3W643.tar.gzを展開してできたj3w-643/j3c_script/以下にworkディレクトリを作成して、その中にcamerawk.j3cを置いているものとして解説を行います。

【リスト 2】camerawk.j3c（先頭部分を抜粋）

import "../lib/key.j3c";
import "../lib/color.j3c";
import "../lib/ssolid.j3c";
import "../lib/zsolid.j3c";
import "../lib/revolutn.j3c";

リスト 2 は camerawk.j3c の先頭部分で、j3w-643/j3c_script/lib 以下にあるライブ
ラリとクラスを使うようにインポート宣言しています。そのあと、クラス間で共有して使われる定数を宣言しています。「 l i b r a r yMessage」はカメラに送る命令で、メッセージ（32ビット整数）に分かりやすい名前を付けています。送る側と受け取る側で利用するため、library Message {final int LookUp
final int LookDown
final int LookLeft
final int LookRight
final int GoBehind
final int GoHome
final int Select}// メッセージ
= 1;
= 2;
= 3;
= 4;
= 5;
= 6;
= 10;library として定数のスコープをグローバルにしています。

Floor（リスト3）は最も簡単なクラスです。
床を作った後は、単にプログラムの終了まで静止しています。1辺1000の正方形を色を変えて4 枚並べています。正方形の登録に sSolid ラ

library const {final int CAMERA = 0;
final int Ref= 1000;
final int PL1= 3000;
final int PL2= 4000;}// インスタンス番号// インスタンス番号// インスタンス番号// インスタンス番号

J3W の内部構造の解説■コンパイラ j3c

いるところがあるかもしれません。j3cを開発する上は、Pascal や Oberon-2 を作った Niklaus Wirth の名著ででJ3C言語のシンボル管理が最も苦労した部分です。

3D アニメーション専用の仮想 CPU を実装していある「アルゴリズム＋データ構造＝プログラム」（記事末

コンピュータ言語の文法は十分に研究されていて、るj3wの仕組みを5回にわたって概説してきました。の Resource ［1］を参照）という本のサンプルの PL/0 とLL(1)文法より柔軟な文法を定義できるLALR(1)文法

今回は、この連載の主役であるJ3C言語のコンパイいう小さなコンパイラと同じような構造になっています。

を解析するプログラムは、yaccやbisonといったコン

j3cはC++で書かれています。j3cのクラスの大まかな関パイラコンパイラ（コンパイラを作成するプログラム）

Linuxでは、多くの言語のコンパイラやインタプリ連を図 B に示します。J 3 C 言語の構文解析はが、文法を定義するファイルから自動的に作ってくれタが無料で利用できるため、言語処理系をあえて作「cParse::parse( 入力ファイル , 出力ファイル) 」が起ます。しかし、生成されたプログラムの動きを理解すろうとする人も少ないと思います。しかし、ゲーム点となっています。ソースの読み込みは、cScanクラスがることは困難です。私は人間的な構造のプログラムになど特定の分野の専用言語を考えるのも面白いもの担当しています。数値や予約語、クラスや変数の名前を認なる再帰下降構文解析法が好きです。

です。コンパイラが高価であった 20年近く前には、識して、 cParseクラスが構文解析する単位となるトーク言語処理系の作成は、アマチュアプログラマにとっン（単語）を返しています。

ラ「j3c」を見ていきます。

3D アニメーション専用の「便利な」高級言語を思い付かなくて、まず「万能な」アセンブラ形式の J3W

構文解析に必要とされる変数や定数の名前を管理するシを公開しました。3年近く後になって、J3C言語を追

j3c は、J3C 言語のソースファイルを読み込んで、ンボルテーブルは 4 つのクラスで構成されていて、j3c の加しました。もっと良いアイデアがあれば、J3W用アセンブリ言語のソースを 1 パスで出力するコンパ

ソースでは最も複雑なパートなっています。クラスの継承、の別の文法を持つ言語に挑戦してみてはどうでしょイラです。そのアセンブリソースを 2 パスアセンブ

局所変数、ライブラリ、クラスのデータを扱うときに「名うか？

ラ（j3dasm）でアセンブルすることでバイナリ（j3d前の有効範囲（識別子のスファイル）が生成されます（図 A）。

コープ）」
を適切に管理するて一般的なテーマであったように思います。

J3C言語は、LL(1)文法（左結合：L、最左導出：L、ためです。この部分は、シ1トークン先読み：1）という文法を使っています。LL

ンボルの管理に関する情報(1)文法の言語は、構文図式（記事末の「J3C 構文リを十分に見つけられなかっファレンス」を参照）に沿って構文解析するプログラたため、無駄な処理をして【図 A】j3d ファイルができるまで50ム（再帰下降法と呼ぶ）を組んでいくことができます。j3c

J3W for Linux 入門講座

【図 B】クラスの関連図（水谷純）

イブラリのHorizonを使っています。Horizon
は、原点を中心とした正方形を生成するため、SetOffset 組み込み関数で登録される頂点座標をずらしています。SetOffsetの機能は、続いて登録される頂点座標に RX、RY、RZ レジス
タ変数の値を加えるというものです。結果として、本来登録されるべき頂点座標がレジスタ変数の分だけ「ずれ」ることになります。原点を中心として作成されるはずの正方形を複数並べることができます。
視点となる Camera クラスは形を持っていません（リスト4）。受信したメッセージに従って回転や移動を行います。視点を持っている（See組み込み関数を実行している）ため、移動や回転に伴って「空間の見え方」が変化します。カ
メラはHeadクラスの局所座標系を移動します。
従って、カメラが動かなくても Head クラスが移動すると、カメラも運動することになります。
カメラの移動中にメッセージを受信すると動【リスト 3】camerawk.j3c（Floor クラス）

class Floor {// 床を生成int INIT() {NewObject(20, 20);
ClearRegisters();
SetPosition();// 中心は原点Priority(10000);// 表示優先順位を低く
RX = -500; RY = -500; RZ = 0;
SetOffset();// 中心は (-500, -500, 0)
sSolid.Horizon(Color.SolidTeal, 500);
RX = -500; RY = 500; RZ = 0;
SetOffset();// 中心は (-500, 500, 0)
sSolid.Horizon(Color.SolidBrown, 500);
RX = 500; RY = -500; RZ = 0;
SetOffset();// 中心は (500, -500, 0)
sSolid.Horizon(Color.SolidNavy, 500);
RX = 500; RY = 500; RZ = 0;
SetOffset();// 中心は (500, 500, 0)
sSolid.Horizon(Color.SolidGray, 500);}
int RUN() {Wait();}int EVENT() {}}作が止まります。例えば「見上げてから元に戻【リスト 4】camerawk.j3c（Camera クラス）

→Pause(200);// 0.2 秒停止
RH = 360;// 右 45 度
Move(rate);break;
case Message.GoBehind :if (behind == 0) {RX = -400;// 後ろ
Move(rate);
RX = 0;RZ = -150;// 上Move(rate);behind = 1;
} else {RX = 400;// 前
Move(rate);
RX = 0;RZ = 150;// 下
Move(rate);behind = 0;};
break;
case Message.GoHome :ClearRegisters();
SetPosition();behind = 0;
break;
case Message.Select :See();
break;}
type =0;
SetCommon(0, RQ);// 割り込み許可}
int EVENT() {type = RL;}→

class Camera {// カメラvolatile int type, behind;final int rate = 1000;// 回転時間
int INIT() {RQ = const.CAMERA + RX;
behind = 0;
ClearRegisters();
SetPosition();See();}
int RUN() {SetCommon(1, RQ);// 割り込み禁止
ClearRegisters();switch (type) {case Message.LookUp :RP = 360;// 上 45 度
Move(rate);
Pause(200);// 0.2 秒停止
RP = -360;// 下 45 度
Move(rate);break;
case Message.LookDown :RP = -360;// 下 45 度
Move(rate);
Pause(200);// 0.2 秒停止
RP = 360;// 上 45 度
Move(rate);break;
case Message.LookRight :RH = 360;// 右 45 度
Move(rate);
Pause(200);// 0.2 秒停止
RH = -360;// 左 45 度
Move(rate);break;
case Message.LookLeft :RH = -360;// 左 45 度
Move(rate);}

2002.2

Linux Japan

51

る」という一連の動作を中断させないため、共有メモリ領域のインスタンス番号の位置に、割り込み禁止フラグとして「1 」を書き込んでいます。動作が終了したら、「0」を書き込んでメッセージが受信可能であることをKeyInクラスに伝えています。
リスト 5 は、2 人の選手とレフリーのクラスです。体と頭の 2 つの部分からできています。
体の部分は Player クラスと、Player クラスを継承した Player2 と Referee クラスの3つがあり、色と動きが異なっています。Z軸に沿って形状を定義するzSolidライブラリのBar881メソッドを使っています。連結した八角柱2つと八角錐を生成しますが、SetScale組み込み関数を使ってX軸方向を半分に圧縮することで、身体らしい扁平な形状としています。Playerクラス（とサブクラス）は、Head クラスを階層構造の下位に生成しています。Head クラスで生成される頭は、単に回転体を使った球で表しています。Headクラスは階層の下位にCameraクラスを生成しています。

Player クラスの動きは、中心（床）から距離 100 だけ離れて、カニ歩きで回るものです。
円周の距離を横に進むあいだに、360度ヘッド回転することで実現できます。ここでは1秒間に円周の 1/4 の距離を 90 度向きを変えて運動します。90 度回転するたびに前進してちょっと横を見て元の位置に戻る、という動きを加えて変化をつけています。 Player2 クラスは、Player クラスと対称な位置で同じ動きをしています。Referee クラスは静止しています。

KeyIn クラス（リスト 6）は、キー入力されたときに視点を持っているインスタンス（Camera クラスのインスタンス）に、キー入力に対応するメッセージを送信します。そのために、選択された Camera クラスのインスタンス
番号を cam 変数に保持しています。1 キー、2キー、3キーのどのキーが入力されたかでインスタンス番号を切り替えています。また今回のサンプルでは、いったんメッセージを受け取ったら「見上げる」、「左を向く」動作のあと、「元【リスト 5】camerawk.j3c（2 人の選手とレフリーのクラス）

class Head extends Revolution {int INIT() {PushRegisters();
sphere(30, 16, 16, RX);
PopRegisters();
child(Camera, 100, 0, 0);
ClearRegisters();
RZ = -170;
SetPosition();}}// 頭// カメラを生成

class Player {// 選手 1
int Initialize(int color) {NewObject(100, 100);// 物体生成zSolid.Bar881(60, 40, 40, 40, 30, 50, color);
RX = 500; RY = 1000; RZ = 1000;
SetScale();RX = color;child(Head, 100, 300, 300);
ClearRegisters();}
int INIT() {RQ = const.PL1;
Initialize(Color.Green);
RX = 200; RH = 1440;
SetPosition();// 位置設定}
int RUN(){
ClearRegisters();
RY = 314; RH = -720;// 周回運動Move(2000);
RX = 150;// 前進
Move(500);
RotHead(500, 80);
RotHead(500, -80);
RX = -150;// 後退Move(500);}
int EVENT() { }}class Player2 extends Player {int INIT() {RQ = const.PL1;
Initialize(Color.Red);
RX = -200;
SetPosition();}}// 選手 2class Referee extends Player {int INIT() {RQ = const.Ref;
Initialize(Color.Cream);
ClearRegisters();
RX = -500;
SetPosition();}int RUN(){Wait();}}// レフリー// 位置設定の位置に戻る」
動作をします。横を向いている
間に次のメッセージを受信して動作を中断することがないように、Camera クラスのインスタンス番号と同じ共有メモリのアドレスを、「メッセージ送信禁止フラグ」として利用しています。
カメラ側でメッセージを受け取る準備ができた場合に禁止フラグを「0 」にします。KeyIn クラスは、禁止フラグが「0」の場合だけメッセージを送信しています。メッセージを受信する側は、メッセージを受け取ると、必ず動作中の命令（移動、回転、停止）を終了して次の命令に移ってしまいます。そのため、都合の悪いタイミングでメッセージを送らないように送信側で52

J3W for Linux 入門講座// 初期位置// 位置設定【リスト 6】camerawk.j3c（KeyIn クラス）

case Key.B :// [B] 背後視点
Send(cam, Message.GoBehind);break;
case Key.H :// [H]
Send(cam, Message.GoHome);
break;
case Key.One :// [1] 選手 1 の視点選択
cam = const.CAMERA + Color.Green;
Send(cam, Message.Select);break;
case Key.Two :// [2] 選手 2 の視点選択
cam = const.CAMERA + Color.Red;
Send(cam, Message.Select);break;
case Key.Three :// [3] レフリーの視点選択
cam = const.CAMERA + Color.Cream;
Send(cam, Message.Select);break;
case Key.Esc :// [Esc]Stop();// ESC キーで終了
break;→class KeyIn {volatile int key, cam;volatile int InteruptMask;
int INIT() {cam = const.CAMERA + Color.Cream;}
int RUN() {key = InKey();// キー入力// 共有メモリを調べて割り込み OK ならメッセージ送信
InteruptMask = GetCommon(cam);if (InteruptMask == 0) {switch (key) {case Key.CursorLeft : // [ ← ]Send(cam, Message.LookLeft);break;
case Key.CursorRight :// [ →]Send(cam, Message.LookRight);
break;
case Key.CursorUp : // [ ↑] 見上げる
Send(cam, Message.LookUp);
break;
case Key.CursorDown : // [ ↓] 見下ろすSend(cam, Message.LookDown);
break;}→}}
int EVENT() {}}【リスト 7】camerawk.j3c（main クラス）

チェックするようにしています。
J3C では、常に main クラスが最初に実行されるクラスとなっています。リスト 7 の main
クラスでは「グラフィックウインドウを開いて、背景の色を設定、視野角（ズーム）の指定」といった初期設定をした後で、これまで見てきたクラスを順次起動しています。Floor は、床、Refree、Player、Player2 という登場人物、キー入力からプログラムの動作を伝えるKeyInクラスといったインスタンスを生成して、後はプログラムの終了を待ち続けます。
では実際に実行してみます。j3w643.tar.gzが展開済みの場合には、j3w-643/j3c_script

class main {int INIT() {GraphMode();BackgroundColor(0x0);
Zoom(0);
ClearRegisters();
new(Floor,100);
new(Player, 100);
new(Player2, 100);new(Referee, 100);new(KeyIn,100);}int RUN() {Wait();}int EVENT() {}}// メイン// 床// 選手 1// 選手 2// レフリー// キー入力クラスに移動して、付録CD-ROMの sample.tar.gzを展開します。j3w-643/j3c_script/workに移動して、以下のように入力して実行してみましょう（画面 2）。

j3cc -r camerawk.j3c
まずレフリーの視点です。Bキーを入力すると、「後ろへ下がって、上に移動」した視点となります。もう一度 B キーを入力すると、「前進して、下に移動」して元の位置に戻ります。
カーソルキーでは、ゆっくり矢印の方向を見て、元に戻ります。1キーでは選手1の視点、2キー
では選手 2、3 キーでレフリーの視点に切り替■終わりに【画面 2】camerawk.j3c の実行例

3Dアニメーションを眺めるだけではなく、視点の切り替えと移動だけでしたが、少し参加できるプログラムになってきました。
物体を設計する場合に、形状の定義をしながら自分の目で見て確認したくなります。作成中のプログラムの動作とは別に、物体の形を個別に確認するためのツールとして objview.j3cが用意されています。次回は、このobjview.j3c の使い方を解説する予定です。

わります。
今回のサンプルでは、頭（Head クラス）は静止したままでした。頭にも色々な動きをつけ［1］ アルゴリズム＋データ構造＝プログラムてみてください。頭は、体（Player、Player2、Niklaus Wirth著／片山卓也訳／科学技術出版

Refree ）クラスの局所座標系で運動します。

原書：Algorithms + Data Structures = Programs, Prentice-Hall, 1976.

2002.2

Linux Japan53J3C は、3D アニメーションキット J3W のコンパイラです。
J3C は、Java風の文法を持つオブジェクト指向言語として設計されています。今月から3回にわたって、J3Cの構文リファレンスを掲載していきます。

■構文規則（syntactic rule）■構文図式の基本形

コンピュータ言語の文法は、構文規則によって決定されます。
構文は、左から右へ、上から下へ矢印をたどって読んでいきます。

a は b である

b は「B」である

構文図式の基本形は次のようになっています。
「連続」、「選択」、「繰り返し」を組み合わせて文法を記述します。

連続

選択

a の後に b が続く

a か b のどちらかを選択する

繰り返し

繰り返し

a の 0 回以上の繰り返し

a の 1 回以上の繰り返し。b は区切りの役割を持つ

構文規則の左側にある名前のことを、「非終端記号（nonterminalsymbol）」と呼びます。また、class や for などの予約語、＝や＋などの記号で表される単語そのもののことを「終端記号（terminalsymbol）」と呼びます。終端記号と非終端記号は、構文図では次のように表します。

終端記号

非終端記号

（terminal symbol）（nonterminal symbol）

囲みの中には予約語または記号が入ります

囲みの中には、他で定義しているものが入ります

任意a か何もなし

構文規則を順次適用していくと、 すべての非終端記号は終端記号に行き着きます。

プログラム

ライブラリ

J3C のプログラムは、「クラスの定義」、「ライブラライブラリは、クラスと異なりインスタンス化することはありません。スコー

リの定義」、「外部ファイルの挿入（import 宣言）」のプはグローバルで複数のクラスからライブラリに属するメソッドと定数を参照す繰り返しから構成されます。

ることができます。クラスのようにデータメンバ（クラス変数）を持つことはできませんが、定数を宣言することはできます。

import 宣言

クラス

import は、別に保存されているソースファイルを指定位置に挿クラスは、インスタンス化されると「スレッド」として独立して動入（インクルード）します。インクルードされたファイルはそこで作します。クラスは独立しているため、すべてのデータメンバとメソッコンパイルされます。同一ファイルが複数回インクルードされた場

ドのスコープはプライベートであり、他のクラスから参照することは合、2 度目からは無視されます。

できません。extends節で宣言済みのクラスを指定した場合、extends
節で指定したクラスからデータメンバとメソッドを継承します。

54

J3W for Linux 入門講座

Field 宣言

Method 宣言／仮引数リスト／仮引数宣言

クラスにはメソッド関数の宣言と変数または定数の宣言を置きます。

クラス内で使用する関数を定義します。型名はintのみです。メソッドは使用前に定義されている必要があります。すべてのクラスには INIT、RUN、EVENT メソッドが必須（継承も可）です。

Field 変数宣言
クラスに属する変数、または定数を宣言します。

定数宣言
変数は、final 宣言されたもの（定数）のみ初期化が可能です。変数の配列は、一次元配列のみ宣言することができます。配列を利用する場合、Javaと異なり、配列の大きさを指定する必要があります。

final宣言された定数は、定数式中に使用することができます。なお、初期化された配列の参照時のインデックスに変数は使用できません。

変数宣言

ブロック

変数の型は int 型（32 ビット整数）のみです。値を変更する通常「{」と「}」で囲まれた部分は「ブロック」と呼ばれ、ブロックの変数は、前に「volatile」を置きます。変数は初期化できません。

内だけで有効な変数、定数を宣言できます。変数、定数の宣言は変数は配列とすることもできます。定数式の部分には配列の要素文より前に置く必要があります。

数を指定します。配列の添字は必ず「0」から始まります。

文リスト
局所変数宣言ブロックの最後まで有効な局所変数を宣言できます。型はintのみです。ブロック内で文リストの前に宣言します。局所変数も定数のみが初期化できます。

文と文の間はセミコロンで区切ります。セミコロンは空文（何もしない文）または、文と文の区切りとして作用します。従って、ブロックの最後の文にセミコロンを付ける必要はありません。

2002.2

Linux Japan55