PC‐9801+TURBO Cグラフィックスに強くなる本〈ゲーム編〉
 4766509005, 9784766509007

Citation preview

啓学出版

啓学出版

第 4 章で使用するパターンデータ 第 3 章のバターンエディタで作ります。

啓学出版

TURBO C は米国 Borland Intarnational 社の登録商標です MS-DOS は米国 Microsoft 社の登録商標です。 そ の他本文中 に登場す る 製品名は一般に開発メ

ーカー の商標です。

はじめに

コンパイラ とい うと、 ほんの少し前 までは、 パーソナルに所有する こと

は夢物語 として語られる だけでした。しかしながら、現在では TURBOC の ような統合環境下で、プログラム の編集、実行、デバックができる手軽 なコンパイラ の登場 により、様相 は一変してしまいました。しかも、MS

-DOS が普及する につれ、マシンを自在に使いこなしたいという 欲求 を実

c

現す る ため の有力な手段として、 言語は不動の地位を占めて きた よう で す。

c

実際、 言語 は様々な欲求に答えて くれます。データベース やエディタだ けではなく、コンパラ や OS までも C 言語で記述される に至って は、C 言

語のスーパースター ぶり を物語っている と言える のか もしれません。 さて、本書では グラフィックス の中でも人気の高い分野である

TV ゲー

ムを テーマ として 掲げています。今までこの分野 は、難解なマシン語でし か開発できない特殊な分野 とされて きましたが、高速な処理 を 要求される

リアルタイム ゲーム でさえ も、工夫次第では C 言語で作る こと が可能 と

なって きました。 もちろんゲーム では、ハードウェア に強く 依存したプロ グラム とな る の は避けられない ことですから、対象 とな る 機種 は PC-9801

シリーズ に限る ことに なります。しかしながら、逆に考えれば、機種に強

く 依存したプログラム も 記述できる とい うのが

c 言語の大きな特徴であ

る とも いえる のです。

本書では、2進数の基礎から始まり、必要 とされる ハードウェア の知識を

c

解説する と共に、 言語の初心者でも 読め る よう に各関数 は簡単な もの ば かり を用意しました。 そして、 それぞれの関数 は機能別に部品化を図った もの とし、これら をプロジェクト ファイル として組み上げてい く ことに

vi もくじ

第 3 章 パターンエディタに挑戦

61

62 BOX ルーチンの作成 割り 込み処理の実際 (点滅カ ソ ルの表示) 74 割り込み関数の定義 76 キ^一の入力を知る に は (高速編) 95 G.V. RAM 上のパターンのコピー 107 グラフィック画面のセーブ/ロード



113 ファイル名の入力 ディ レクト リ内のファイル の探索

第 4 章 TV ゲーム の世界 アニメーション処理

115

125 126

キャスト 演算子

130 マップエディタに挑戦

133

GDC によ る スム^ー ズス クロ^ー ル ヒーロー登場

敵の出現 まとめ

索引

173

165

171

155

145

69

もくじ

はじめに もくじ

第 1章

iii

v 0 と 1だけの世界

1

2 進数 (binary number) 2 進数の単位の呼び方

文字コード

4

6

7

2 進数 と16進数

8

マシン の心臓部を探る

10

PC-9801の G.V.RAM と は

11

第 2 章 実践グラフィックス基礎編 グラフ ィックス 処理 を始める 前に グラフ ィックス 処理 を開始する に は

far 指定の ポイ ンタ変数

15 16

18

20

色の混ぜ合わせ 23 ドット から直線へ 25 色の混ぜ合わせ実践編 29 縦方向に直線を描く に は 31 ドット 座標の定義 35 シフト 演算子の効用 39 色の成分チ エック 41 キー の入力を知る に は カーソル の表示

割り込み処理 の実際

43

46

52

マウス を組み込めば、お絵書き ツール ので き 上り

53

vi もくじ

第 3 章 パターンエディタに挑戦

61

62 BOX ルーチンの作成 割り 込み処理の実際 (点滅カ ソ ルの表示) 74 割り込み関数の定義 76 キ^一の入力を知る に は (高速編) 95 G.V. RAM 上のパターンのコピー 107 グラフィック画面のセーブ/ロード



113 ファイル名の入力 ディ レクト リ内のファイル の探索

第 4 章 TV ゲーム の世界 アニメーション処理

115

125 126

キャスト 演算子

130 マップエディタに挑戦

133

GDC によ る スム^ー ズス クロ^ー ル ヒーロー登場

敵の出現 まとめ

索引

173

165

171

155

145

69

6 第 1章



0 と 1 だけの世界

2進数の単位の呼び方

さて、コンピュータ気分に浸ったところで話を先に進めましょう。我々

人間の世界では物事の関係 を表すのに色々な単位を用いています。 たとえ ば重さ を表すのに kg、 秒、などですが、コンピュ ー 分、 時間を表すのに は時、 タの世界でも いろいろな単位を使っています。 コンピュータの世界の基本単位である 2進数1桁のこ と は、 ビット (bit:)

と呼んでいます。これ は binary-digit の略です。取り うる 値 は0 か、1の2つだ

けしかありません。これから本書では1桁の2進数 とか、2桁の2進数 と は言 わ ずに、このビッ ト の単位を使っていきます。したがって1桁の2進数 は1ビッ

ト、2桁の2進数 は2ビッ ト とい う ことに なります。 2進数4桁ではニブル (nibble) といいます。そして、8桁ではバイ ト (byte)

といいますが、このバイ ト はコンピュータの世界ではビッ ト と並んでよ く

使われる 単位です。たとえば、PC-9801のメモリ 上の記憶単位がバイ ト 単位

(8桁の2進数) ですし、マシン語 も バイ ト 単位で構成されています。 また、 英文字や、特殊文字を処理する単位 も バイ ト 単位です。 これらのほかに は ワード (word) とい う 単位がありますが、これ は処理

系によ って 4ビット、12ビット、32ビット など、いろいろな桁数があります。

PC-9801では16ビット をひとつのワード としてい ます。 これら4つの単位 (ビット、ニブル、バイ ト、ワード) は、 コンピュータ

の世界では常識 として扱われていますから、 その言葉の意味を覚えておか なければなりません。

図1-4

2 進数の単位

2 第]章.0 と]だけの世豊 この方法では、イ ンタープリ タ本体 と 高級言語で書かれたソースプログ ラム だけです みます から、メモリ は少なくて すみます。ただし、翻訳しな

がら実行するという 過程では、実行時に 翻訳時間が加わる ことに なります

から、その分スピード はどうして も 遅く なります。

現在のパソコン に は、 ほとんど イ ン タープリ タ型 BASIC が付いて く る ので少なく と も一度 は イ ン タープリ タ型言語を使ったことがあ る のではな

いでしょ う かゝ。

図1-1 インタープリタの実行過程 使って みる と インタープリタ型言語 は非常に手軽に使え、しかも、修正 や変更 も いたって簡単におこなえる こ とがわ かります。 これ に対し、ソースプログラム をあらかじめ全部マシン語に翻訳して、 その翻訳したマシン語のプログラム を実行するという タイプを コンパイ ラ

型 といいます。コンパイラ によ ってマシン語に翻訳する作業を コンパイル と呼んでいますが、 ソースプログラム は、 コンパイラ によ って未定議ラベ

ル等を含む中間的な オブジェクト ファイル (拡張子 OBJ) に 編集されます。 さらに、リンカー によ って、最終的なマシン語プログラム が作られる ので す。 こ の最終的なプログラム を実行プログラム (拡張子 EXE) といいます。

実行プログラム は イ ン タープリ タ型と は違って、実行時に翻訳する時間

図1-2 コンパイラの実行過程

3

がかかりませんから、 たいへん高速に実行されます。

その反面、何らかのバグ を生じた場合に は、翻訳される 前のソースプロ グラム を取り出して 修正し、再びコンパイルして実行を確認するという一

連の面倒な作業をしなければなりません。 とはいって も、 得られる安全性 や高速性は、 何物に も代え難い とい える でし ょう。

本書では この コンパイ ラ型 c 言語の特徴 をいかして リ アル タイム ゲー ムを作りながら c 言語の可能性をさ ぐろう とい うわ けです。

4 第1章

0 と 1 だけの世界

2進数 (binary number)

リアルタイム ゲーム に限らず、高速な グラフ ィ ックス 処理を する に は、

やはり コンピュータの世界、すなわち 0 と1だけ の世界である 2進数 (binary

number) の基礎的なこと を知らなければなりません。2進数 と は どの よう な ものかを知る ため に、 ここで、4桁の2進数 と10進数を対比させて みま しよ

う。 】0進数と 4 桁の 2 進数との対応

表 1一1

0 …… 0000 1 …••• 0001 2 …… 0010 3

…… 0011

4

… •••

5

…… 0101

6

…… …•••

7

8 9 10

…… •••…

1000 1001

… 1010

•••

0110

11 1011 12 …••• 1100 13 …… 1101 14 …••• 1110

0111

15

0100

••••••

… •••

1111

いかがです か、4桁の2進数で表すことができる の は表1-1に ある ように全

部で0から15 ま での16種類です。 このよ う に2進数 は、0 と1だけ が並んでいる だけですから、何が どれを表

してい る のか人間に とって は区別がしに く いですね。 さらに2進数の世界を知る ため に、 1桁の2進数 どうしの足し算を考えて み

ましょう。

+

0 0 0

図】- 3

+

1 0 1

+

0 1 1

+

1 1 10

】 桁の 2 進数の足し算

はじめ の3つの演算 は10進数 とまった く 同じですが、 注目したいのは最後

の1+1です。これ は10進数では2 となり ますカ又 2進数の世界ですから2にな

壊 TH蒯 A集足D

火帮対8理 C

呼天法琳呼

就U比 中EWW(以 向£ 乂 射中ば 根輝

M

納i剛 度め―磅翻照

3*梦4

灼尊駆心忸源厘も舞加ゆ# #4 监t%

#*

n然 莒



「 ―

.

,





X









」:「

-

'



献金若籌農屮博細第蒙貧*理曲贏修 承!縁毒* 蒙 龜 尊 観石 朮eUK緑K •灘赫報* 殊!注 冢* ,巧 制!爆被サ騎至験 I 西然&* •骗■gth* 専X 由 «M



.

. .'

6 第 1章



0 と 1 だけの世界

2進数の単位の呼び方

さて、コンピュータ気分に浸ったところで話を先に進めましょう。我々

人間の世界では物事の関係 を表すのに色々な単位を用いています。 たとえ ば重さ を表すのに kg、 秒、などですが、コンピュ ー 分、 時間を表すのに は時、 タの世界でも いろいろな単位を使っています。 コンピュータの世界の基本単位である 2進数1桁のこ と は、 ビット (bit:)

と呼んでいます。これ は binary-digit の略です。取り うる 値 は0 か、1の2つだ

けしかありません。これから本書では1桁の2進数 とか、2桁の2進数 と は言 わ ずに、このビッ ト の単位を使っていきます。したがって1桁の2進数 は1ビッ

ト、2桁の2進数 は2ビッ ト とい う ことに なります。 2進数4桁ではニブル (nibble) といいます。そして、8桁ではバイ ト (byte)

といいますが、このバイ ト はコンピュータの世界ではビッ ト と並んでよ く

使われる 単位です。たとえば、PC-9801のメモリ 上の記憶単位がバイ ト 単位

(8桁の2進数) ですし、マシン語 も バイ ト 単位で構成されています。 また、 英文字や、特殊文字を処理する単位 も バイ ト 単位です。 これらのほかに は ワード (word) とい う 単位がありますが、これ は処理

系によ って 4ビット、12ビット、32ビット など、いろいろな桁数があります。

PC-9801では16ビット をひとつのワード としてい ます。 これら4つの単位 (ビット、ニブル、バイ ト、ワード) は、 コンピュータ

の世界では常識 として扱われていますから、 その言葉の意味を覚えておか なければなりません。

図1-4

2 進数の単位

文字 コード 7

1 文字コード 画面上に表示されている英文字や漢字 はコンピュータが扱う場合に、や はり 2進数 として扱っています。ただ、CRT の画面上に表示された時が2進

数ではなく、文字の形をしてい る に すぎません。 すなわちひとつひとつ の文字に、2進数を1対1に対応させている のです。

もちろん、2進数1桁では、0 か1の2通りしか対応できませんから、ある 程度

十分な桁数である8桁の2進数 (1バイト) を使っています。 この対応のさせ方 はいろいろ考えられますが、処理系や コンピュータに よって ばらばらでは まったく互換性が なくなります から、 そこに はち ゃ ん と標準化がなされています。

PC-9801では英文字や特殊文字を表すコード として アスキーコード に 準 拠してい ます。たとえば、英文字 A のアスキー コード は2進数では01000001

c は01000onです。ですから、信号 として

となり ます。b は010000io、

01000001が送られる と CRT 上に英文字の A が表示される わけです。 なお、 ソースプログラム はエディタを使って、 このアスキー コード で編

集します。

8 第 1章



0 と 1 だけの世界

2進数 と16進数

と こ ろで、 2進数を 扱う時に は0 と1の羅列ですから人間に とって は とても

苦痛です。しかも、書き表す時に は、 かなり の スペース を使いますので、 むだが多い とも いえます。 そ こで2進数のお もし ろい性質を使っ て、2進数を16進数で表記す る とい

う手品の ような、 まったく 不思議な 方法を使う わけです。ここではあっさ

り とそ の種あかしをして みましょう。

通常 よ く 使われる メモリ の基本単位である8ビット の2進数を考えて みま

w

す。 まず代表選手として、00001111とい う半分力 である状態を考えます。

さらに、この8桁の2進数を上位4桁 と下位4桁の2つ に分けて みます。

上位4ビッ ト

図1-5

下位4ビッ ト

8 桁の 2 進数を 2 つに分ける

この図で、下位の2進数1111は表の

より、10進数では15に対応してい

る ことが分かります。

さて、この下位の4ビット 11H に1を足す とどうなる でしょう か。

+

ini 1

Ujoo

-



桁上が り 力 生じる

図1-6 下位 4 ビットに 1 を足す

上の ように計算されますが、得られた結果 は下位4ビット では足りずに、 上位4ビッ ト の方へ と桁上がり を生じる こと が分かります。 このこ と は、 2進数の4桁が集まって、ひとつ の数字を構成すると見立て

20 第 2 章 実践グラフィックス基礎編



far 指定のポインタ変数

では、実際に G.V. RAM へアクセスして みましょう。 G.V. RAM は特殊 なメモリ である こと は、 1章でも 述べましたが、ここでも う一度 G.V. RAM

の整理をして みます。

表とI

G.V.RAM アドレスとプレーンとの対応 開始セグ メン

プレーン

開始 オ フセ ッ

トア ドレス トア ドレス

第 0 プレーン

A800H

第 1プレーン

BOOOh

第 2 プレーン

B800H

第 3 プレーン

EOOOh

0000h 000Oh 000Oh 0000h

VRAM の大きさ 800Oh 800Oh

8000H 800Oh

応色 青色 赤色 緑色 輝度

表2T に示した よ う に、G.V.RAM に はプレーンが4 つ(初期の PC-9801 では青色、赤色、緑色の3つ) あります。これら の4つのプレーンにより、 ひとつ の画面が構成される のです。また、PC-9801シリーズ では、この画面

が第一画面 と第二画面のニ組 (PC9801、PC-9801U では第一画面のみ) 用

意されており、 ポート A4h, A 6H でコントロールしてい ます。

、、ベー 表

画面の I/O コントロール ト値

値^^^

出力

0 1

A 4H

A6H

第一画面表示 第二画面表示

第一画面アクセス 第二画面アクセス

それぞれのプレーン の大きさ は800Oh バイ ト なのですが、 画面上に表示さ

れる 範囲 は、640X400モード では、0

7CFFh となり、640X200モード では

その半分 となり ます。 また、第0〜第2プレーン の3プレーン だけ を使う と、

8色モード となり、4プレーン全部使う と16色モード となり ます。

各メモリ は8ビット 単位で構成 され、そのビッ ト イメージ が TV 画面上

10 第]章



0 と]だけの世界

マシンの心臓部を探る

マシン の心臓部、 すなわち CPU

が、やはり

(Central Processing Unit) のこ とです

c 言語を十分に利用する に は cpu の特徴ぐらい は知っておく

べき でしょう。PC-9801シリーズでは、8086系 CPU が使われています。こ

の cpu の特徴 は、 なんといって も セグ メント によ る メモリ 管理 の方法で

す。

こ の概念 は、メモリ を cpu が一度に数えられる範囲で分割し、その分割 単位で管理するという 考え方です。 この分割単位を セグメント と呼び、 そ のセグメント内の相対的な位置を オ フセ ッ ト といいます。

ですからメモリ を直接参照する場合に は、 8086系 CPU 上でプログラム が動いている 限り、 どのセグメントを使う のか とい う セグメント アドレス

と、そのセグメント内の相対的なオ フセ ットアドレス の2つ を与える作業が

必要 となって きます。PC-9801上で TURBO C をイ ンストールする時に、 メモリ モデル を指定した と 思いますが、このメモリ モデル もプログラム で セグ メント を どの よう に使ってい く のかの指定に 他なり ません。

c 言語 は高級言語であ る に も かかわらず、メモリ を直接ア ク セス する こ とができる ように なっています。当然 のこ とながら直接メモリ をアクセス する場合に は、 セグメント アドレス と セグメント 内の オフセットアドレス

を設定する こ と になり ます。

さて、本書が テーマ として掲げている

TV ゲーム は、実は、このメモリ

に直接アクセス する作業に ほかならない のです。

PC-9801の G.V.RAM と は 11

PC-9801の G.V.RAM とは PC-9801のメモリ に は、 グラフィックス を表示する ため の特別な メモリ が用意されています。これを グラフィックビデオ ラム(以下 G.V.RAM と

略記) といいますが、 このメモリ は、 他のメモリ と同様にバイト (8ビット の2進数)単位で数値を記憶させる こと ができます。 ただ、他のメモリ と 異 なる の は、記憶した数値のビット イメージ (2進数の1桁) が画面上のドッ

ト (点) に対応してい る とい う こと です。

CRT 画面でグラフィックス 処理をするという こと は、この G. すなわち、 V.RAM に数値を書き込む とい う ことなのです。 G.V.RAM は、色別に 4つのグループに分かれます。それぞれをプレーン

o

と呼んでいます が、初期状態ではプレーン は青で、セグメント アドレス オ フセ ットアドレス Oh から始まっています。一般的に セグメントア ドレス と オ フセ ットアドレス は:で区切って表記してい ますから、この場合

に は、A800:0000h となり ます。また、各プレーン の大きさ は 800Oh バイ ト と なっています。

同様に、プレーン1が赤で、アドレス は B000:000Oh

0 1

プレー

0

1

2

77

78

79

OOOOh

OOOIh 0051h

0002h 0052h

004Dh 009Dh

004Eh 009Eh

009Fh

005Oh

004Fh



• •



TV 画面 (640X400ドットモード) • •

• • • 398 399

7c 60H 7CB0h

7c61H 7CB1h



7c62H 7cB 2H

7CADh 7CFDh

7CAEh 7CFEh

640X400ドットモード表示

7CAFh 7CFFh

0 と]だけの世界

12 第]章

ン2が緑で、アドレス は

そして、プレーン3が輝

B800:0000h

度用で、アドレス は E000:000Oh E000:7FFFh となり ます。

ここで、各オ フセ ットアドレス が000Oh 7FFFh とな る よう に、セグメン ト アドレス を設定してある ことに 注意して ください。 この場合の、各プレーン の オ フセ ッ トアドレス と TV 画面との対応 は次 の ように なっています。なお、実際に画面に表示される メモリ の範囲 は、

グラフィックス システム に640X400ドッ ト モード を 選択した場合、オフ

セット アドレス で000Oh

0 1

• •

7CFFh (図1-7)

となり ます。

0

1

2

77

78

79

OOOOh

OOOIh 0051h

0002h

0052H

004Dh 009Dh

004Eh 009Eh

004Fh 009Fh

005Oh •

• •

TV 画面 (640X200ドット モード) •



• • •

198 199

3DE0h 3E 30H



• •

3DE1h 3E 31H

3DE 2H 3E 32H

3E2Dh 3E7Dh

3E2Eh 3E7Eh

3E2Fh 3E7Fh

図1-8 640X200ドットモード 第 1 ページ表示

0 1

0

1

2

77

78

79

3E80H

3E81H

3EDD

3ED1h

3E82H 3ED 2H

3ECDh 3F1Dh

3ECEh 3F1Eh

3ECFh 3F1Fh • •







TV 画面 (640X200ドットモード) • •

• • •

198 199

7c 60H 7CB0h



• •

7c 61H 7CB1h

7c62H 7cB 2H

7CADh 7CFDh

7CAEh 7CFEh

図1-9 640x20(1ドットモード 第 2 ページ表示

7CAFh 7CFFh

ドッ ト から直線 へ 27

を次に示します。なお、16進数1桁 は1章でも やりましたが4ビッ ト に相当し

てい ます。 すなわち、16進数1桁で画面上の4ドット (点4 つ分) に対応して いる わけです。

LIST2-3.C /* int型の ポイ ンタを 使って横 に青い 直線を 描く

*/ #include

/* 必要とされる ヘッ ダフ ァイル を includeする */

♦include

extern void grfinit(void);

/* 外部関数の宣言 */

extern void pointer set(void); extern int far *i_blue;

/* i nt型のB面用 ポインタの宣言 */

void main(void) { int i, j; grphinit(); pointer set();

-

0; i < 40; ++i) { *(i_blue + i) = Oxffff;

for(i

/* 使用する変数の宣言 */ /* グラフ ィックス システム を 初期化する */ /* ポインタ をセッ ト する */ /* 1ライン分Oxffffを描き 込む */ /* B面へOxffffを 描き 込む */

I getch(); closegraph();

/* キー の入力待ち */ /* グラフ ィックス システム の終了とする */

LIST2-3.PRJ LIST2-0 LIST2-1 LIST2-3

この例では、 ポインタに16ビッ ト 長 の int 型を使い筆を動かす ループ回 数 は LIST2-2の半分の40 とし、さらに、ポインタの指している フィールド

に格納する数値 は、16ビット 長の数値で、2進数で表す と すべてがI.とな る

Oxffff (十進数で65535) としています。 このよ うに、 ポインタ変数を換えた分、ループ回数が半分 となり ました

壊 TH蒯 A集足D

火帮対8理 C

呼天法琳呼

就U比 中EWW(以 向£ 乂 射中ば 根輝

M

納i剛 度め―磅翻照

3*梦4

灼尊駆心忸源厘も舞加ゆ# #4 监t%

#*

n然 莒



「 ―

.

,





X









」:「

-

'



献金若籌農屮博細第蒙貧*理曲贏修 承!縁毒* 蒙 龜 尊 観石 朮eUK緑K •灘赫報* 殊!注 冢* ,巧 制!爆被サ騎至験 I 西然&* •骗■gth* 専X 由 «M



.

. .'

第 2 章 実践グラフィックス基礎編

グラフィックス 処理の醍醐味 は なんといって も、 TV ゲーム に代表され る 2次元

+ a の世界を創造する ことでしょう。

新しい世界を創造する のですから、それこそ、 神様の ような気分が味わ える わけです。しかも、それが製品化される となると、 もうこ たえられ ま

せん。

この章では、グラフ ィックス の最も 基本 とな るドット (点) に焦点を当

てて話を進めていきます。大海 も一滴の水から できている ように、あら ゆ る グラフィックス 処理の基本単位 はドット ですから、このドット を自由に

扱う ことができれば、グラフ ィックス 処理 も 自在に こなせる ことに なる の です。 では、 TURBO C を使う ため の、簡単な下準備 から 始める ことにしま

しょう。

32 第 2 章 実践グラフィックス基礎編 LIST2-5.C /* 縦に青い 直線 を 描く

*/ #include #include

/* 必要と される ヘッ ダフ ァイ ルをincludeす る */

extern void grfinit(void); extern void pointer set(void);

/* 外部関数の宣言 */

extern char far *c_blue;

/* char型のB面用 ポインタの宣言 */

void main(void) {

/* 使用する変数の宣言 */ /* グラフ ィックス システム を 初期化する */ pointer_set(); /* ポインタ を セット する */ /* 縦方向へ0x80を 描き 込む */ for(i =0, j = 0; i < 399; ++i, j += 80) { /* B面へ0x80を 描き 込む */ *(c_blue + j) |- 0x80;

int i, j; grphinitO;

} getch( ); closegraph();

/* キー の入力待ち */ /* グラフ ィックス システム の終了とする */

}

LIST25PRJ LIST2-0 LIST2-1 LIST2-5 をたてる前にプログラム を示しましたが、ジックリ とプログラム を読んで

みて く ださい。

ここで、ひとつ の G.V.RAM メモリ に格納されている8ビット の画面上 の対応を調べて みましよ う。

図2-1で示したよう にひとつ のメモリ上8ビッ ト のデータの内、最上位の ビッ ト は画面上では左側 に位置してい ます。

LIST2-5.C では一番左側 の線 を描 くこ と を目的 と してい ます から、 CRT 上一番左側 に位置 す る メモリ に格納されている 数値の最上位ビッ ト

グラフィックス処理を始める 前 に 17

識す る ことなし に、 これらのドライバ を使う こと も できます。 ところで、これらの*.BGI とい う ファイル は、その まま の形ではなく、 マスター ディスク の DISK 3に ある BGI.ARC 内に 圧縮して格納されてい

ますから、 使用する場合に は圧縮前のファイルに戻さなければなりません。

圧縮前のファイル に戻す といって も、難しい ことではなく、同じディ スク にある UNPACK.EXE を使って簡単に復元する ことができます。

次に A ドライブ から B ドライブへのファイル の展開例 を示しておき ま すから参考にしてください。

まず、DISK 3をドライブ A に、ワーク ディスクをドライブ B に セット

し、次のように実行します。 BGエ.ARCの展開例

A>UNPACK BGI.ARC

B:*.BGI0

展開した*.BGI のファイル は、以下のプログラム ではカレント ディレ クトリ に ある ものとして扱っていきます。

グラフィックス 関係の関数を使う ため に は、 この他に、TURBO C 付属 のライブラ リフ アイル GRAPHICS 丄旧 と、ヘッ ダフ アイル GRAPHICS. H が必要 となり ますから、所定のディ レクト リ に 用意しておいて く ださい。

なお、 本書に掲載されているプログラム だけ をプログラ ミ ング するならば、

メモリ モ デル は スモールモデル となり ます。

ドッ ト 座標の定義 37

図2-5 ビットアドレスが 2 の場合の例 さらに、ビットアドレス が2の場合に は図2-5の ように なり ます。

ですから、画面上にドット を表示する に は、X 座標を8で割った余り とし

て求められる ビットアドレス 分、0x80 を右方向に シフト して求められる 数

値 と、あらかじめ求めておいた G.V. RAM 上のメモリ アドレス との間で、 論理演算 OR を実行するという ことに なる のです。 これらをプログラム に したのが次の LIST2-6です。

LIST2-6.C extern char far extern char far extern char far extern char far

*c_blue; *c_red;

*c_green;

*c itsty;

/* /* /* /*

char型B面用 ポインタ の宣言 char型R面用 ボイン タ の宣言 char型G面用 ポイン タ の宣言 char型エ面用ボ インタの宣言

*/ */ */ */

int memory adr, x, y, bit adr, sign = 1;

/* メモリ ア ドレス 格納用 */ /* X座標格納用 */ /* y座標格納用 */ /* ビットアドレス 格納用 */ /* ドット を セット する か否かのサイン用*/

void dot_plot(int color) { int i, j; unsigned char dot_set = 0x80,

/* 使用変数の宣言 */

dot res =

-

0;

x

&

7;

bit adr dot set

»-

bit_adr;

dot_res

へ=

dot set;

/* 変数bit_adrへx+8の余り を 求める */ /* 0x80 を ビット アドレス分右方向へシフト する */ */ /* リセ ッ ト 用データ

グラフィックス処理 を開始する に は 19

テム を初期化してい る関数です。関数 initgraphO の3番目の引数''”は、 先

に 出て きた グラフィックスドライバの*

.BGI の格納されている ディレク

トリ パス を示している のですが、 この例の ように、 なに も 指定しない場合 に はカレント ディレクトリ に、 グラフ ィックスドライバが入っている こ と

を意味してい ます。

initgraph()の実行結果 は、関数 graphresult()で得られ ますから、その結 果にしたがって、エラー処理をします。

ここでは、単純にメ ッ セージ を表示して関数 exit(l)で、呼び出された親 プロセス へ制御を戻しています。 もし、エラーメッセージでプログラム が

終了した場合に は、 もう一度、各ファイル の存在を確かめて ください。

42 第 2 章 実践グラフィックス基礎編

color & 00000001 = 1 で B プレーン の発色有り

color & 00000001 = 0 で B プレーン の発色無し 同様に R, G, Iプレーン は次の ように なります。

color & 00000010 = 1 で R プレーンの発色有り color .& 00000010

=

0 で R プレーンの発色無し

color & 00000100

1 で G プレーンの発色有り

color & 00000100

= =

color & 00001000

=

1 でIプレーンの発色有り

color & 00001000

=

0 でIプレーンの発色無し

0 で G プレーンの発色無し

さて、 ここで if 文 の動作を思い出してください。

if (条件式) { く条件が真 とな る時の処理»; }

else

{

C条件が偽 とな る時の処理>; } 条件式の評価 は、(条件式±0) で真 となり、(条件式 =0) で偽となり ま すから、簡単に色の成分 に従った処理ができる のです。すなわち、式の演

算の結果が真(丰0)であれば対応するプレーン のビット をセット し、偽(= o) であれば対応するプレーン のビッ ト をリセ ットするわけです。

44 第 2 章 実践グラフィックス基礎編 break;

case '7 1: y;



break;

case 1 8 * :

-y;

break; Case * 9 * :

——y; ++x; break;

default: break; }

if(y > ymax) if(y < ymin) if(x > xmax) if(x < xmin) return(r2);

y = ymax; y ymin; x = xmax; 匸

x = xmin;

/* /* /* /*

y座標の 最大値 のチェック */ y座標の 最小値のチェック */ x座標の最大値 のチエック */

x座標の 最小値 のチェック */ /* キー 情報を 関数の戻り値とする */

}

ここでは、TURBO C の関数 getch() でキー ボード からの情報を得てい ます。そして、方向別の処理をする為に、テンキー の1, 2, 3, 4, 6, 7, 8, 9を対応させて、座標 (X, Y) の更新処理をしてい ます。 また、 せっかく

取得した キー情報 は、関数 の戻り値 として返しています。 これで、 キー情報に従って座標 を更新する関数と、座標に従ってドット

を表示する 関数が そろいましたから、 それぞれをリ ンクさせてコンパイル

および実行 といき ま しょ う。main 関数 とプロジェクト ファイル は次 の よ

う になり ます。

LIST2-8.C #include

/* 必要とされる ヘッ ダフ アイル をincludeする */

extern void grphinit(void); /* 外部関数の宣言 */ extern void pointer_set(void); extern void dot_plot(int); extern int xycset(int, int, int, int);

キーの入力 を知る に は 45 /* main関数の定義 */

void main(void) It

int keydata = 0, xmax = 639, ymax 399, xmin 0, ymin = 0, color = 2; grphinit(); pointer set(); while(keydata != Oxlb) {

-



/* キー 情報格納用 */ パ X座標の最大値 */ /* y座標の最大値 */ /* X座標の最小値 */

/* y座標の最小値 */ /* 描写する点の色の指定 */ /* グラフ ィックス システム の初期化 */ /* ボイ ンタの設定 */ /* [ESC]キー のチェック */ /* 点の位置および キー データ設定 */ keydata xycset(xmin, xmax, ymin, ymax); /* 点の描写 */ dot^lot(color);

-

closegraph();

/* グラフ ィックス システム の終了 */

}

LIST2-8.PRJ LIST2-0 LIST2-1 LIST2-6 LIST2-7 LIST2-8

色の混ぜ合わせ 23



色の混ぜ合わせ

さて、これらの4つの色 は混ぜる こと も できます。といって も、絵の具の

ように パレ ッ ト 上で混ぜ合わせる わけではありません。混ぜよう とする各 ポイ ンタの同じ オ フセ ットアドレス に、 データを書き込むことに より 実現

します。 たとえば、赤 と 青のプレーン の同じオ フセ ッ トアドレス に書き込めば、

紫色が発色す る のです。こ れら色の混ぜ合わせ方 は、表2-3の よ う に16通 り あり ます。

この表2-3の ように、 4つのプレーン の重なり方 は16通り ありますが、 こ

こで PC-9801シリーズでは4096色が使える こと を思いだしてください。実 は、 この16種類の色 は仮り の色だったのです。 ですから、 これら16種類 の プレーン の重なり 方に対して、 この表の通り に色を割り付けてあっただけ

のこ とな のです。しかし、 まったく 色がばらばらでは、話の進めよう があ り ませんから、本書では この初期状態の色を基準に話を進めていきます。

表2-3 色 薄い 黒







輝度









輝度

X

X

X

X



X

X

X

O

X

X

X





X

X

X

X



X

X

X





〇 〇

X



〇 〇

X

X

X

X



X

X



〇 〇 〇 〇

〇 〇 〇 〇

〇 〇 〇 〇 〇 〇 〇 〇

明い 水色 X X X 水色 〇 X 明い 黄色 X 黄色 〇 〇 明い 白 X グレー 〇 〇 〇 プレーン の 使われます。 緑 の が 3 (注) 8 色モード 時は、 青、 赤、

X



八、、

X

薄い青 薄い赤 薄い 紫 明い緑

さて、これら16通り の色、 すなわち絵筆の色を把握したら、次 は筆の動

かし方です。では次の意味を考えて みて ください。.

24 第 2 章 実践グラフ イックス基礎編 c_blue + 1;

匚blue

+

1;

/* 8 ビット 長 のchar型のfar ポインタ変数を1増やす */ /* 16ビット 長のchar型のfar ポインタ変双 を:L 増やす */

ここで、i_blue, c_blue は共に、LIST2-1で設定した B 面に対する far

指定の ポインタ変数です。 B 面 は初期状態では青に対応してい ます。両者 共に、 B 面を指している ポインタです。 それぞれ は、 まったく同じように見えますが、これら の違い は何でしよ

うか?

それ は ポインタの指し示している フィールド の扱い方です。すな

わ ち、プレーン0の G.V.RAM を8ビッ ト 長 の char 型で取り扱う のか16

ビッ ト 長の int 型で扱う のか とい う 違いな のです。 この違い だけ はしっか

り と把握しない と 混乱を まねきますので注意して く ださい。

ここ に示した式は、式 として は どちら も 変数を単純に +1するという 意味 ですが、 この場合の変数 は ポイ ンタ変数ですから式の意味する と こ ろが変 わって きます。ポインタ変数の場合、1増える こと は、フィールド 内の次 の

データを指すことに なる のです。

ですから、フィールド を8ビッ ト 長 の char 型 とすれば、次 の8ビッ ト の データを指し、16ビッ ト 長の int 型であれば、次の16ビッ ト 長のデータを指

してい る ことに なります。 もし、関数のエントリー を指している ポインタ であれば、次 の関数を指すことに なる のです。 プログラム では見かけ上同じ に見えて も、 結果的に、メモリ の刻み方 は

16ビット 長のほうが、8ビット 長の二倍の刻み となり ます。

50 第 2 章 実践グラフィックス基礎 編 sfsin cmax

=

0,

=

15,

=

1;

cmin

/* シフトサ イ ン用 */ /* カラー 番号の最大値 */ /* カラー 番号の 最小値 */ /* 現在選択されているドット カラー */

0,

color

/* グラフ ィックス システム の初期化 */ /* 各プレーン へ の ボイン タの設定 */ cursorxor(memory_adr, bit__adr, color); /* カーソル の表示 */ while(keydata != Oxlb) { keydata = xycset(xmin, xmax, ymin, ymax); /* キー 情報を取得 */ /* bios98keyへのコマンドの設定 */ kinf.cmmd = 2; sfsin = bios98key(&kinf ); /* シフト キー の情報を 取り込む */ if(csr_on == 1) ( /* カーソル サイン カ七nならばカーソル の消去 */ cursorxor(memory_adr, bit_adr, color);

grphinit(); pointer set();

}

switch(keydata) ( /* キー 情報に従って処理 を する */ case *x*: case *X*: 1) ++color; if(color < cmax

-

break;

case * z * : case 'Z * : if(color > cmin + 1)

―color;

break;

defaelt : break; }

if(keydata != , ,) { /* [space]キー が押されていれば カーソル を 消去 */ sign = 0; if(sfsin & 1) sign » 1; else dot_plot(color); cursorxor(memory_adr, bit adr, color); } }

closegraph();

/*

グラフ ィックス システム の 終了とする

LIST2-10.PRJ LIST2-0 LIST2-1 LIST2-6 LIST2-7 LIST2-9 LIST2-10

*/

26 第 2 章 実践グラフィックス基礎編

ここでは、プロジェクト ファイル名を、LIST2-2.PRJ としましたカヌ、 こ れは適当に設定して ください。プロジェクト ファイル を TURBO C へ登録

(GRPH + P) したら、 コンパイル & 実行です。以後、プロジェクト ファイ

ル名は、 main 関数のファイル名に、”PRJ” とい う 拡張子を付けた もの と します。なお、プロジェクト ファイル はメイン関数 と は別のファイル とし てディスク上にあらかじめ保存しておきます。

実行して みる と CRT 画面の上方に 青い横線が描けた はずです。さて、画

面上の点をドットい いますが、 このドッ ト は G.V. RAM のメモリ のビッ ト イメージ に対応してい る というの は、 もう知っています ね。ドット (点) の集 ま り は1本の線にな る の は 書く まで も ありませんカミ、ここ でメモリ と 画

面との対応を もう一度、図1-7で確認して ください。

図1-7から、メモリ の並び は画面上横方向に対応し、画面のひとつ のライ ンはメモリ 総数0x50バイ ト (十進数で80) で構成される ことがわ かります。

また、0x50バイ ト を超えた時に は、メモリ は連続してい ますが、画面上は

不連続 となり次 のライン の左側へ移っています。このこ と は、画面上 Y 方

向に メモリ を刻んだ場合、0x50バイ ト のメモリ 増加に なるこ と を示してい ます。

以上のこ と を踏まえ、 LIST2-2を参照して みる と、 どの ようなプロセス で青い線を描いたのかが よ く わかる と 思います。 ところで、LIST2-2では ポインタ変数を直接動かす (変数の数値を増や

す)のではなく、i とい う変数を作用させて間接的に動かしています。こう すれば、各 ポイ ンタ変数の値 は常に一定値とな り、TV 画面左上に固定され る ことに なる からです。いわば、版画における 見当がつ く とい う こと な の

です。

さて、実際に G.V.RAM メモリ に描き 込んでいる命令は* (c_blue

+

i) =Oxff です。LIST2-1で定義した青色 の絵筆 に 相当 する ポインタ変数 は、i_blue または、c_blue でした。どちらを使って も 青い線を描く こと はで きます。ここ で問題 とな る の が、 これ ら ポインタ の 扱い方 です。

LIST2-2では c_bhie を使いましたから、例 として i_blue を 使った もの

ドッ ト から直線 へ 27

を次に示します。なお、16進数1桁 は1章でも やりましたが4ビッ ト に相当し

てい ます。 すなわち、16進数1桁で画面上の4ドット (点4 つ分) に対応して いる わけです。

LIST2-3.C /* int型の ポイ ンタを 使って横 に青い 直線を 描く

*/ #include

/* 必要とされる ヘッ ダフ ァイル を includeする */

♦include

extern void grfinit(void);

/* 外部関数の宣言 */

extern void pointer set(void); extern int far *i_blue;

/* i nt型のB面用 ポインタの宣言 */

void main(void) { int i, j; grphinit(); pointer set();

-

0; i < 40; ++i) { *(i_blue + i) = Oxffff;

for(i

/* 使用する変数の宣言 */ /* グラフ ィックス システム を 初期化する */ /* ポインタ をセッ ト する */ /* 1ライン分Oxffffを描き 込む */ /* B面へOxffffを 描き 込む */

I getch(); closegraph();

/* キー の入力待ち */ /* グラフ ィックス システム の終了とする */

LIST2-3.PRJ LIST2-0 LIST2-1 LIST2-3

この例では、 ポインタに16ビッ ト 長 の int 型を使い筆を動かす ループ回 数 は LIST2-2の半分の40 とし、さらに、ポインタの指している フィールド

に格納する数値 は、16ビット 長の数値で、2進数で表す と すべてがI.とな る

Oxffff (十進数で65535) としています。 このよ うに、 ポインタ変数を換えた分、ループ回数が半分 となり ました

マウス を組み込めば、 お絵書き ツールので き 上り 55

void mousexyset(int xO, int yO) /* マウスカーソルの位置 を 設定する */ {

/* /* /* /*

4; msinf.cmmd msinf.abs_h crs = xO; msinf.abs_v_crs = yO; bios98mouse(&msinf);

コマンド を マウスカーソル位置の設定とする

*/

x方向位置を x0とする */ y方向位置 を yO とする */ マウスカーソル位置を 座標(x0, yO)とする */

} int msonoff

=

/* マウス の オン/オフ管理用 */

1;

/* マウス システム の終了とする */

void mouseterm(void) l

msint.cmmd = 0; /* コマンド を初期化とする */ bios98mouse_init(&msint); /* マウス システム の初期化 */ }

/* マウス 用ユーザー 定義関数 */ void msdotplot( int status. /* マウス の状態 */ int ェft buton, /* マウス左ボタン の状態 */ int rgt buton, /* マウス 右ボタン の状態 */ unsigned abs h crs, /* マウス 用 カーソルの水平軸座標 */ unsigned abs v crs ) /* マウス 用 カーソルの垂直軸座標 */ { /* msonofful なら カーソル表ホし座標を 更新 */ if(msonoff == 1) { { 1) /* カーソルが表示状態 であれば カーソルを 消去 */ if(csr_on == cursorxor(memory adr,bit_adr,color); J

x

=

y

»

abs h_crs; abs v crs;

if(rgt_buton == -1) { if(status & 8) {

/* X軸 カーソル位置を 新たにX座標 とする */ /* y軸 カーソル位置を 新たにy座標 とする */ /* 右 ボタン が オン であれば カラーを 変更する

*/

++color;

if(color

==

16)

color

=

0;

} } ±f(lft_buton == -1) sign = 1; /* 左ボタン がonなら点の描写とする */ sign = 0; else /* signに従って点を 描写する */ dot plot(color); cursorxor(memory_adr, bit_adr, color); /* カーソル を 表不する */



else if(csr_on == 1) ( /* カ ソルが表示 されていれば カ ー ソルの消去 */ cursorxor(memory_adr, bit_adr, color); }



色の混ぜ合わせ 実践編 29

色の混ぜ合わせ実践編

次 に、色の混ぜ合わせ を、プログラム を通して実際に 試して みます。

LIST2-4です。 LIST2-4.C /* 色の混ぜ合わせ

*/ # include

/* 必要とされる ヘッダファイル を includeする */

♦include

/* 外部関数の宣言 */

extern void grfinit(void); extern void pointer set(void);

/* int型B面用 ボイン タ の宣言 */ /* int 型R面用 ポインタの宣言 */ /* int 型G面用 ボイン タ の宣言 */

extern int far *i_blue; extern int far *i_red; extern int far *i_green;

void main(void) { int i; grphinitO; pointer_set();

-

0; i < 40; ++i) { Oxffff; *( i_blue + i) + i) = Oxffff; *(i_red

for(i

/* 使用する変数の宣言 */ /* グラフ ィックス システム を 初期化する */ /* 各 ボイン タを 初期化する */ /* 1ライン分Oxffff を 描き 込む */ /* B面へ Oxffff を 描き 込む */ /* R面へ Oxffff を 描き 込む */

}

getch(); closegraph();

/* キー の入力待ち */ /* グラフ ィックス システム の終了とする */

)

LIST2-4.PRJ LIST2-0 LIST2-1 LIST2-4

30 第 2 章 実践グラフィックス基礎編 宣言部を除けば、プログラム として は、LIST2-3に R 面をアクセス する ため の一行を加えた だけです。

このプログラム が実行する こと は、 最初に 青い線 を描き、青い線 と画面

上同じ位置 (同じ オ フセ ットアドレス) に、赤い線 を描く とい う作業です。 実際に、このプログラム を実行して みる と画面上に表れた直線 は紫色 と なっている はずです。これ は、 画面上同じ位置に、 青、赤、 を発色したた

め、 青 と赤を混ぜ合わせたことに なって紫色の直線 と なった わけです。 こ

こでの ポイ ント は各色別の絵筆が、CRT 画面上、同じ位置 に線を描いた と いう ことです。 すなわち各 ポイ ンタに加算してい る 数値が同じなのです。 このよ う に色を混ぜ合わせるプロセス は、CRT 画面上同じ位置の必要な プレーンに対して、 数値を 書き込む こ と によ って簡単に実現できる のです。

なお、この各プレーン の組み合せ方によ る 色合い は表2-3を参照して く ださ い。

参考のため に、LIST2-4.C に輝度用の ポイ ンタを使った場合や、その他、

色々 な組み合せで調べ み る の も よ い方法だ と 思い ま す。

•ノない戸除一

公……



ドサ d 绰

吟談準 惘タ限間IS合競ュ 二

e爨映

业fedb”曲祭曲网MMB” 施 4

習歩/国農院し書モ報

i zu パ ババ‘ダバニ二2め ペ球単つ,激ぺい X由なバ 加Xお磁 公ャ驟M鮒降思一即/



,

3

q

ダルも加嬢S*03

;

,

, セル当W 少少Mrjy5rg $ 叮 け JM



,巧 A WEヤヤ州 キでK气エヾほ でハメ結苗サ" 73Nエ一員 号 if tkey^A, ymax) if(y < ymin) if(x > xmax) if(x < xmin) return(r2);

y = ymax; y ymin; x = xmax; 匸

x = xmin;

/* /* /* /*

y座標の 最大値 のチェック */ y座標の 最小値のチェック */ x座標の最大値 のチエック */

x座標の 最小値 のチェック */ /* キー 情報を 関数の戻り値とする */

}

ここでは、TURBO C の関数 getch() でキー ボード からの情報を得てい ます。そして、方向別の処理をする為に、テンキー の1, 2, 3, 4, 6, 7, 8, 9を対応させて、座標 (X, Y) の更新処理をしてい ます。 また、 せっかく

取得した キー情報 は、関数 の戻り値 として返しています。 これで、 キー情報に従って座標 を更新する関数と、座標に従ってドット

を表示する 関数が そろいましたから、 それぞれをリ ンクさせてコンパイル

および実行 といき ま しょ う。main 関数 とプロジェクト ファイル は次 の よ

う になり ます。

LIST2-8.C #include

/* 必要とされる ヘッ ダフ アイル をincludeする */

extern void grphinit(void); /* 外部関数の宣言 */ extern void pointer_set(void); extern void dot_plot(int); extern int xycset(int, int, int, int);

キーの入力 を知る に は 45 /* main関数の定義 */

void main(void) It

int keydata = 0, xmax = 639, ymax 399, xmin 0, ymin = 0, color = 2; grphinit(); pointer set(); while(keydata != Oxlb) {

-



/* キー 情報格納用 */ パ X座標の最大値 */ /* y座標の最大値 */ /* X座標の最小値 */

/* y座標の最小値 */ /* 描写する点の色の指定 */ /* グラフ ィックス システム の初期化 */ /* ボイ ンタの設定 */ /* [ESC]キー のチェック */ /* 点の位置および キー データ設定 */ keydata xycset(xmin, xmax, ymin, ymax); /* 点の描写 */ dot^lot(color);

-

closegraph();

/* グラフ ィックス システム の終了 */

}

LIST2-8.PRJ LIST2-0 LIST2-1 LIST2-6 LIST2-7 LIST2-8



46 第 2 章 実践グラフィックス基礎 編

カーソルの表示

LIST2-8.PRJ を起動して みる と、 テンキー に 従った直線 は自由に引け

る のですが、ドッ ト を表示する 現在位置が不明な のが非常に見づらい こと

そこで次の課題 として、 現在のド ッ ト 位置を示すカー が分かる と思います。

ソル表示へ と話を進める ことにしましょう。 まずカーソル の表示で考えなければならない の は、 カーソル と、 すでに

表示されている絵 との重ね合わせです。 この時のカーソル は グラフ ィ ック スを描く為のカーソルですから、他 の絵 と 同様に G.V.RAM へ表示しな

ければなり ません。当然 のこ とな がら、カーソル を表示すればすでに G.V.

RAM 上に存在してい る絵 は壊されてしまいます。カーソル を移動させる と後に は古い カーソル の残骸が残っている わけです。 そこで、 なんらか の

工夫をして古いカーソル の部分を元の状態に復元する ことに なり ます。

ここでは最も 単純な背景 と の XOR (排他的論理和) を とる 方法を試みて みます。次 に XOR の真理値表を示しますから参照して く ださい。 表2-4

XOR の真理値

X 0 0 1 1

Y

X (

+)

0

0

1

1 1 0

0 1

Y (+ ):XOR

この真理値表を よ くみ る と X と Y が異なれば演算結果が1となり、X と

Y が同じであれば、0 となっている ことがわ かります。 今、カーソル のデータを X、背景の絵を Y とし、X と Y との XOR を ほ どこした演算結果を

z として考えて みましょう。

z

この場合、 がカーソル表示 によ って破壊された データ とい う ことに な り ます。ここで問題 とな る の は、背景 Y を X との演算 XOR によ って破壊 したデータ Z をいかにして元の Y とい う 背景 の データに 復元 する かとい

88 第 3 章 パターンエディタに挑戦

ですから、 このドット の点滅を一時的に ス ト ップさせなければならない のです。 このドッ ト の点滅 を 管理してい る のが LIST3-10.C の dotcrson

(), dotcrsoffO とい う関数で、単純に コールする だけ としました。 編集エリア を パターンで埋める 関数 は allpaintO となり ます。 この al-

lpaint() を利用して、編集エリア を5種類のパターンで埋める よ うにしたの が次 のプロジェクト

LIST3-11.PRJ です。なお、編集エリアを埋める時の

ドット の色 は、 現在選択されているドッ ト の色 とい う ことにしました。

LIST3-11.C ♦include #define MAKE_RIGHT 0x39 #define MAKE_LEFT 0x38

♦define MAKE_F1

0x62

♦define MAKE_F2

0x63 0x64

♦define MAKE_F3 ♦define MAKE_F4

#define MAKE_F5

0x65 0x66

extern void grphinit(void); extern void pointer set(void); extern void zoombitdt(void);

extern void dispwaku(void); extern void initset(void); extern void dotcsr(int); extern void initcsr(void); extern void termcsr(void); extern void initkey(void);

extern void termkey(void); extern void rcolor(void); extern void Icolor(void); extern void initcolor(void); extern void allpaint(int, int, char, int); extern int

wwindow, color, keydat,

48 第 2 章 実践グラフィックス基礎 編 /* /*

0x1800,

0x1800

0001100000000000

0001100000000000

*/ */

};

union { unsigned int

/* カーソルデータのシフト 用 */

crs;

unsigned char crs2 [2] ; } pt; int i;

csr_on for (i

/* 使用変数の宣言 */ A

=

- -

pt .crs

if (c

& 1)

if (c & 2) if (c if (c

/* カーソル の表示状態 を示す */

1;

0; i

&

4)

& 8)

++j; if (c &

1)


50 ymax / boxmov(); break; case MAKE_UP: if( by < 0) by 50 boxmov();



8) by

- ymax / 8;

break;

} }

boxcursol(box adr); dotcrson(); }

/* ボックス 型カーソルの移動 */ void boxmov(void) {

boxcursol(box_adr); box_adr -by*8*80+bx+ mwindow; abchange(selectw, box adr); boxcursol(box_adr); keydat»l; )

extern void xorvramdt(int, int, int);

/* ボックス 型カーソルの表示 */ void boxcursol(int k) {

int i, j; if(box_inf 1) { j (ymax 1) * 80; for(i = 0; i < xmax / 8; ++i, ++j) { xorvramdt(k + i, Oxff, 1); xorvramdt(k + j, Oxff, 1); 二

-

}

j = xmax / 8 + k + 79; k +- 80; xorvramdt(k, 0x80, ymax xorvramdt(j, 0x01, ymax )

else box inf

1;

--

0;

2);

2);

カーソルの表示 51

どう です か、 カーソルが表示されたでけでも グレード がグッ と上がった

のではないでしょうか。実際にプロジェクト

LIST2-10.PRJ を走らせて、

いろいろな直線を描いて みて く ださい。また、X, Z キーで色の変化を、ス ペース キーでカーソル の ON/OFF を管理する よ うにして あり ます から、

これら も 同時に試して ください。LIST2-10.C まで進んで く る と、 G.V.

RAM の構成 や 仕組み がか なり ハッキリ としてき た ので はないでしょ う か?



52 第 2 章 実践グラフィックス基礎編

割り込み処理の実際

次のチャレンジ として は、 ゲーム を作る上で欠かせない割り込み処理の

方へ と話しを進めていき ま しよ う。 割り込み と聞く と、 あまり良い イメージがありま せんが、コンピュータ の世界では、欠かせない ものなのです。

たとえば、cpu があ る 処理を実行してい る時にストップキーが押された とかゝ、どこ かから通信を受けた と力、 、割り 込み は様々 な要因で発生します。

CPU は これら の要因に従って、割り込みベクタテーブルをサーチし、要因 別の処理へ と制御を移す のです。 そして、割り込み処理が終了すれば、 ま た元の処理へ と制御を移し粛々 と処理を続行してい きます。 これらの処理 は、 高速に行われる ため に人間に とって は割り込まれた と

いう より も、あたかも 並列に処理が進行してい くよう に 思えます。割り込

みと は、 まさに、割り込み を意識させない所に意義があ る のです。

図と6 割り込み処理の実行過程

98 第 3 章 パターンエディタに挑戦 case MAKE_ZOOM: zoombitdt(); break; default : break;

} } termkey(); termcsr(); closegraph(); }

LIST3-13.PRJ list2-0 list2-l list3-0 list3-2 list3-4 list3-6 list3-8 list3-10 list3-12 list3-13

さて、これでパターンエディ タの編集機能 は最低限、そろった わけです。

でも、 せっかく ここ まで作ったのですから、 もう 少し編集機能を充実させ たい ものです。 そこで、編集エリア の回転移動、平行移動、上下反転機能 を付加する ことにしました。なお、左右の反転 は、回転移動 と上下反転 に

よって実現する ことができます から、ここ では機能 として は省いてありま

す。

LIST3-14.C extern char far *c blue; extern char far *c red; extern char far *c green; extern char far *c itsty; extern int

xmax.

54 第 2 章 実践グラフィックス基礎編 extern void dot_plot(int); /* ドッ ト 描写用外部関数の宣言 */ extern void cursorxor(int, int, int); /* カーソル表示用外部関数の宣言 */ extern int x,

/* /* /* /* /* /* /* /*

y. color, sign, bit_adr,

csr on, memory adr; MOUSE_INIT msint;

union REGS reg; MOUSE_INFO msinf;

外部変数の宣言 */ X座標格納用 */

y座標格納用 */ 指定色格納用 */ カーソル の表示 または消去を 示す */

ビット ア ドレス */ カーソルの表示状態 を 示す */

メモリ ア ドレス */

/* マウス 初期化用構造体定義 */ /* レジスタ用構造体定義 */ /* マウス 情報格納用構造体定義 */

void msdotplot(int, int. int, unsigned, unsigned); void mouseinit ( unsigned int unsigned int unsigned int unsigned int If

mxmin r mxmax, mymin, mymax )

/* マウス の可動範囲x方向の最小値 /* マウス の可動範囲X方向の最大値 /* マウス の可動範囲y方向の最小値 /* マウス の 可動範囲y方向の 最大値

*/ */

*/ */

msint.cmmd = 0; /* コマン ド を 初期化とする */ bios98mouse init(&msint); /* マウス 環境の初期化 */ msint.cmmd = 12; /* コマンド を 関数の登録 とする */ msint.cal1_cond = Oxlf; /* msdotplot を 呼び出す条件 */ msint.mouse fun = msdotplot; /* 関数名msdotplot を 登録 */ bios98mouse_init(&msint); /* msdotplot を ユーザー 定義関数とす る */ msint.cmmd = 16; /* コマンド を X方向可動範囲の設定とする */ msint.min_h_pos = mxmin; /* x方向の 可動範囲の最小値を 指定する */ msint.max_h_pos = mxmax; /* x方向の可動範囲の最大値を 指定する */ bios98mouse init(&msint); /* 可動範囲の設定 */ msinf.cmmd =4; /* コマ ンドを マウスカーソル位置の設定とする */ msinf.abs_h crs = mxmin; /* マウスカーソル のX方向初期値を 設定 */ msinf.abs_v_crs = mymin; /* マウスカーソル のy方向初期値 を 設定 */ bios98mouse(&msinf); /* マウスカーソルの初期位置の設定 */ x = mxmin; /* ドッ ト 描写用x座標の初期化 */ y = mymin; /* ドッ ト 描写用V座標の初期化 */ bit adr mxmin & 7; /* ビ ッ トア ドレス 初期化 */ memory_adr = (mxmin » 3) + mymin * 80; /* メモリ アドレス 初期化 */

-

}

マウス を組み込めば、 お絵書き ツールので き 上り 55

void mousexyset(int xO, int yO) /* マウスカーソルの位置 を 設定する */ {

/* /* /* /*

4; msinf.cmmd msinf.abs_h crs = xO; msinf.abs_v_crs = yO; bios98mouse(&msinf);

コマンド を マウスカーソル位置の設定とする

*/

x方向位置を x0とする */ y方向位置 を yO とする */ マウスカーソル位置を 座標(x0, yO)とする */

} int msonoff

=

/* マウス の オン/オフ管理用 */

1;

/* マウス システム の終了とする */

void mouseterm(void) l

msint.cmmd = 0; /* コマンド を初期化とする */ bios98mouse_init(&msint); /* マウス システム の初期化 */ }

/* マウス 用ユーザー 定義関数 */ void msdotplot( int status. /* マウス の状態 */ int ェft buton, /* マウス左ボタン の状態 */ int rgt buton, /* マウス 右ボタン の状態 */ unsigned abs h crs, /* マウス 用 カーソルの水平軸座標 */ unsigned abs v crs ) /* マウス 用 カーソルの垂直軸座標 */ { /* msonofful なら カーソル表ホし座標を 更新 */ if(msonoff == 1) { { 1) /* カーソルが表示状態 であれば カーソルを 消去 */ if(csr_on == cursorxor(memory adr,bit_adr,color); J

x

=

y

»

abs h_crs; abs v crs;

if(rgt_buton == -1) { if(status & 8) {

/* X軸 カーソル位置を 新たにX座標 とする */ /* y軸 カーソル位置を 新たにy座標 とする */ /* 右 ボタン が オン であれば カラーを 変更する

*/

++color;

if(color

==

16)

color

=

0;

} } ±f(lft_buton == -1) sign = 1; /* 左ボタン がonなら点の描写とする */ sign = 0; else /* signに従って点を 描写する */ dot plot(color); cursorxor(memory_adr, bit_adr, color); /* カーソル を 表不する */



else if(csr_on == 1) ( /* カ ソルが表示 されていれば カ ー ソルの消去 */ cursorxor(memory_adr, bit_adr, color); }

G.V.RAM 上のパターンのコピー 103

このプログラム では、 ビッ ト 操作をしてい ますから、コンピュータの扱っ

てい る 数値が、2進数であった とい う こと を忘れてしまう と、ほとんど、プ ログラム として は分かり にくい もの となってしまいます。では、さらに、

次 の LIST3-15.C に進んでく ださい。

LIST3-15.C #include

#define MAKE_ _CR #define MAKE_ _CL

0x39 0x38 #define MAKE_ Fl 0x62 #define MAKE_ _F2 0x63 #define MAKE_ _F3 0x64 #define MAKE_ _F4 0x65 #define MAKE._F5 0x66 #define MAKE_ _SHIFT 0x70 Idefine MAKE_ _ZOOM 0x29 Idefine MAKE_ ROLL 0x13 Idefine MAKE_ _RIGHT 0x3c Idefine MAKE_ LEFT 0x3b Idefine MAKE__UP 0x3a Idefine MAKE_ DOWN 0x3d Idefine MAKE _HANTEN 0x16 一 extern void grphinit(void); extern void pointer set(void); extern void zoombitdt(void); extern void dispwaku(void); extern void initset(void); extern void dotcsr(int); extern void initcsr(void);

extern void extern void extern void extern void extern void extern void extern void extern void extern void

termcsr(void); initkey(void); termkey(void); rcolor(void); Icolor(void); initcolor(void); allpaint(int, int, char, int); ptn copy(void); rolling(void);

マウス を組み込めば、 お絵書き ツールので き 上り 57

プロセス は今までの もの と変わり があ り ません。

さて、プロジェクト ジェクト

LIST2-10に LIST2-11を 関連 させた ものが、プロ

LIST2-12.PRJ です。 LIST2-12.C

#include

♦include #include ♦define

ON

/* 必要とされる ヘッ ダフ ァイル をIncludeする */

1

extern void grphinit(void); extern void pointer set(void);

/* 外部関数の宣言 */

extern void dot_plot(int); extern int xycset(int, int, int, int); extern void cursorxor(int, int, int); extern void mouseinit(

unsigned unsigned unsigned unsigned

int, int, int, int

);

extern void mouseterm(void); extern void mousexyset(int, int);

/* 外部変数の宣言 */ /* X座標格納用 */ /* y座標格納用 */ /* メモリ アドレス */ /* ビット アドレス*/ /* カーソルの表示状態 を 不す */ /* カーソル表示の管理用サイン(1でon, 0でoff) */

extern int X,

y, memory_adr, bit_adr, csr_on, sign;

int color =4;

void main() { KEY_INFO kinf; int sfsin, keydata 0,

xmax

--

639,

/* /* /* /*

キー 情報格納用構造体定義

シフト キー 情報 */ キー データ管理 */

x座標最大値 */

*/

58 第 2 章 実践グラフィックス基礎編 ymax = 399, /* v座標最大値 */ xmin = 0, /* x座標最大値 */ ymin = 0; /* y座標最小値 */ grphinit(); /* グラフ ィックス システム の初期化 */ pointer_set(); /* 各プレーンへのポインタの設定 */ mouseinit(xmin, xmax, ymin, ymax); /* マウス システム の初期化 */ cursorxor(memory_adr, bit_adr, color); /* 力-ソ ノレ の表示 */ kinf.cmmd = 2; / * 機能コー ド セット */ while(keydata != Oxlb) { /* [ESC]キーチェ ツク */ keydata = xycset(xmin, xmax, ymin, ymax); /* キー 情報セッ ト */ sfsin = bios98key(&kinf ) ; パ シフトキー 情報の取り込み */ disable(); /* 割り込み 禁止 */ if(csr_on == ON) { /* カーソル サイン カ== VEND) j 一= VEND; *(long far *)(c_blue + j) = *(long far *)(c_red + j) = *(long far *)(c_green + j) » *(long far *)(jitsty + j) = } break; case 1: for(k = 0, j = px + py; k < PTY; if(j >= VEND) j -= VEND; *(long far *)(c_blue + j) = + j) *= *(long far *)(c_red *(long far *)(c_green + j) *(long far *)(c itsty + j)

--

} break; case 2: for(k « 0, j = px + py; k < PTY; if(j >- VEND) j = VEND; *(long far *)(c_blue + j) = *(long far *)(c_red + j) = *(long far *)(c_green + j) = *(long far *)(c_itsty + j) =



} break; default : break;

} ++pn; if(pn > 2) pn r = 0; ) else ( px

=

keep__px;

-

0;

CONTINUE) {

j+-80, ++k) { pattern[0].dtb[k]; pattern[0].dtr[k]; pattern[0].dtg[k]; pattern[0].dti[k];

j+=80, ++k) {

pattern[1].dtb[k]; pattern[1].dtr[k]; pattern[1].dtg[k]; pattern[1].dti[k];

j+-80, ++k) { pattern[2].dtb[k]; pattern[2].dtr[k]; pattern[2].dtg[k]; pattern[2].dti[k];

キーの入力を知る に は (高速 編) 91

LIST3-12.C extern int

,

keydat

xmax. ymax,

wwindow. shift_key; extern char far *c_blue;

extern char extern char extern char extern void extern void

far *c_red; far *c_green; far *c_itsty;

dotcrson(void); dotcrsoff(void); extern void zoombitdt(void); void abchange(int, int); void boxcursol(int); void boxmov(void); int box_inf, box_adr == 36, fukugen == 10, selectw == 19,

mwindow == 36, bx == 0, by

==

0;

/* 編集パターン を 指定エリア へ退避、 または 退避 エリア から編集エリ ア へバ ターン を 読み込む

*/

void ptn_copy(void) int i, j, k, w; #define MAKE_ READ 0x13 #define MAKE_ WRITE 0x11 #define MAKE_ FUKUGEN 0x20 #define MAKE_ ZOOM 0x29 idefine MAKE_ RIGHT 0x48 idefine MAKE_ LEFT 0x46 idefine MAKE UP 0x43

92 第 3 章 パターンエディタに挑戦 0x4b

#define MAKE_DOWN

dotcrsoff «); 1; box inf 1; w boxcursol(box adr); while(shift_key == 1) ( switch(keydat) { case MAKE_READ: case MAKE_ZOOM: boxcursol(box adr); abchange(fukugen, wwindow); abchange(wwindow, box adr);

boxcursol(box adr); MAKE_ZOOM) zoombitdt(); if(keydat w = 0; 1; keydat

-

break; case MAKE_WRITE: boxcursol(box_adr); abchange(fukugen, box adr); abchange(box adr, wwindow);

boxcursol(box_adr);



1; w keydat break;

-

1;

case MAKE_FUKUGEN: boxcursol(box adr); 0) abchange(wwindow, fukugen); if(w abchange(box adr, fukugen); else boxcursol(box adr);



keydat = 1; break; case MAKE_RIGHT: xmax / 8) bx » 0; if(++bx > 44 boxmov(); break; case MAKE_LEFT: xmax / 8; if( bx < 0) bx » 44 boxmov();

-

--

break;

case MAKE_DOWN:

-

ヒーロー 登場 163 extern void termkey (void) ; extern int

car_disp (int, int);

extern void scroll (int, int, int); extern int

map_data_load (void) ;

extern char gdchz;

int

mwindow = 36;

void ma in ( ) {

int i, j, k, 1, m, n, py, pyO, pye, r; unsigned int kyori;

if (inportb (0x31)

& 0x80)

else

gdchz

= 0;

gdchz

=

0x40;

grphinit ( ) ;

pointer_set () ; clrscr () ;

print f .("¥n 八. ターン テ’ータ ロ••ド") ;

if (gload ( )

== 0) {

= m = n = 0, 1 = mwindow; k < 4; ++k, 1 += for (i = 0, j = 1; i < 11; ++i, j += 4) { n = pattern in(m++, コ); if (n ! = 0) break;

for (k

}

if (n != 0) break; }

map_data_load ( ) ; clrscr () ;

initkey () ; gotoxy (1, 1) ;

printf ("RALLY keydat

=

9 8 ")

1;

pyO = 80 * VSTADR; while (keydat != MAKE_ESC) {

scroll (80 * VSTADR, pyO, 0);

= PTY0; = PTY0; px = 38 ; pyO = 80 * VSTADR; pye = 400 * 80 - 80 * keydat = 0x70; i

py

SCLDOT;

NPAT)

{

94 第 3 章 パターンエディタに挑戦 /* bからaへ のパターン の コピー ★/ void abchange(int a, int b) {

int i, j, k; for(j =0, k = 0; j < ymax; ++j) { for(i = 0; i < xmax / 8; ++i, ++k) *(c blue + a + k) = *(c blue (c_red + a + k) = *(c_red *(c green + a + k) = *(c green *(c_itsty + a + k) = *(c_itsty }

k += 80 } }

- xmax

/

8;

{

+ + + +

b b b b

+ k); + k); + k); + k);

G.V. RAM 上のパターンのコピー 95

G. V.RAM 上のパターンのコピー ここで、G.V. RAM 上のパターンを コピーする手順を考えて みます。ま ず、画面上にある 編集したパターン A を単純に A' の位置へ と コピーする わけですから、 それぞれの、基準 とな る アドレス は それぞれのパターン左

上のアドレス を求めれば よさそう です。となり のアドレス は単純に +1すれ ば よ く、1ライ ン下のアド レス は G.V.RAM の構成 から +80すれば よ いか らです。ここで問題 とな る の は、 編集エリア のアドレス は固定ですから簡

単に求 ま り ます が、移動すべ き A'のアドレス を どの よ う に 決定 する かで す。

G. V. RAM

図3-1 A を A'へコピーする 表3-3

[shift] + [ 2 ] [shift] + [ 4 ] [shift] + [ 6 ] [shift] + [ 8 ] [shift] + [W]

BOX カーソルの操作方法

box カーソルを下方向へ移動す る box カーソルを左方向へ移動する box カーソルを 右方向へ移動する box カーソルを上方向へ移動する 編集エリ アのパターン を box カーソルで示されるエリア へ コピーする

[shift] + [R] [shift] + [F] [shift] + [Z]

boxカーソル位置のパターン を 編集エリ アに コピーする 直前の コピー動作に よって コピーし た パターン を 元に 戻す boxカーソルのパターン を 編集エリアへコピーする と と も に拡大表示する

168 第 4 章 TV ゲームの世界 #define PTYO

42*80*8

#define VSTADR

16

♦define MAKE_RETURN Oxlc

extern int keydat, px; extern int pattern_in(unsigned int, int); extern int pattern_out(unsigned int, int); extern int gload(void); extern void initkey(void); extern void termkey(void); extern int car_disp(int, int); extern void scroll(int, int, int); extern int map_data_load(void); extern void enemy_disp(int); extern void frandom(void);

extern char gdchz;

int

mwindow = 36;

void main() { k, 1, m, n, py, pyO, pye, r; int i, unsigned int kyori;

if(inportb(0x31)

&

0x80) gdchz

else

gdchz

= 0; = 0x40;

grphinit(); pointer_set();

clrscr(); printf("¥n八. ターン データ ロ••ド "); if(gloadO ==0) { for(k = m = n = 0, 1 = mwindow; k < 4; ++k, 1 += NPAT) { for(i =0, j = 1; i < 11; ++i, j+=4) { n = pattern_in(m++, j); if(n != 0) break; }

if(n != 0) break; } map_data__load(); clrscr(); initkey(); gotoxy(1, 1);

G.V.RAM 上のパターンのコピー 97 space;

int xmax

64,

ymax 64, xmin = 0, ymin = 0;

void main() {

grphinit(); pointer_set(); dispwaku(); initset(); zoombitdt(); initcsr(); initkey(); initcolor(); keydat

-

1;

while(keydat != 0) { switch(keydat) { case MAKE_RIGHT: rcolor(); break;

case MAKE_LEFT: Icolor(); break;

case MAKE_F1: allpaint(wwindow, color, Oxff,

0);

break; case MAKE_F2:

allpaint(wwindow, break; case MAKE_F3: allpaint(wwindow, break; case MAKE_F4: allpaint(wwindow, break; case MAKE_F5: allpaint(wwindow, break;

case MAKE_SHIFT: ptn_copy « ); break;

color, Oxaa, 1);

color, 0x55, 1);

color, Oxaa, 0);

color, 0x55, 0);

98 第 3 章 パターンエディタに挑戦 case MAKE_ZOOM: zoombitdt(); break; default : break;

} } termkey(); termcsr(); closegraph(); }

LIST3-13.PRJ list2-0 list2-l list3-0 list3-2 list3-4 list3-6 list3-8 list3-10 list3-12 list3-13

さて、これでパターンエディ タの編集機能 は最低限、そろった わけです。

でも、 せっかく ここ まで作ったのですから、 もう 少し編集機能を充実させ たい ものです。 そこで、編集エリア の回転移動、平行移動、上下反転機能 を付加する ことにしました。なお、左右の反転 は、回転移動 と上下反転 に

よって実現する ことができます から、ここ では機能 として は省いてありま

す。

LIST3-14.C extern char far *c blue; extern char far *c red; extern char far *c green; extern char far *c itsty; extern int

xmax.

173

索引 こ 構造体 127

あ アスキー コード 7

コピー 95

アニメーション処理 126

コンパイ ラ 2

い 色の成分のチェック 41

-の実行過程

色の混ぜ合わせ 29 インタープリタ 1

-の実行過程

し シフト 演算子 39,48

2

インラインアセンブラ

171

え エンハンスト グラフィック チャ ジャー 16 お

オープン

107

オフセット

——形状データ

セグメント

56

148

17

10

——アドレス

10,21

そ ソースプログラム 1

型変換 130

て ディップスイッチ 149 ディレクト リ内のファイル表示

76 76

115

ーー割り込み 76,113



147

せ セーブ 107,113

48

重ね合わせ処理 155

キー情報

初期化作業 18

——コマンド のパラメータ

拡大表示 64

キーボード

初期化 19

スモールモデル

か カーソル 46,69,84



条件式の評価 42

——コマンド

10,21

オブジェクト ファイル 2

——の表示

実行プログラム 2

す スクロール 145

10

——アドレス

2

コンパイ ル 2

キャスト

130

クローズ

107

デバイスドライバ 53

と 動作クロック 148

グラフ ィックス システム

16,18

グラフ ィックスドライバ

16,19

グラフィックチャージャー 16 グラフィックビデオ ラム

11

ドット 座標

35

ドット 表示

56

に ニブル 6

は バイ ト 6 パターンエディタ 61

100 第 3 章

パターンエディタに挑戦

dotcrsoff(); abchange(fukugen, wwindow); e = xmax /8-1; for(j = 0, iO = fukugen, 〇〇 =■ wwindow; j < ymax; ++j, 〇〇 80, iO += 80) { kpb = *(c_blue + iO + e); kpr = *(c_red + iO + e); kpg = *(c_green + iO + e); kpl = *(c_itsty + iO + e); for(i =0, iw = iO, ow = 〇〇; i < xmax / 8; ++i, ++iw, ++ow){ b = *(c_blue + iw); r = *(c_red + iw); g = *(c_green + iw); 1 *(c_itsty + iw); *(c_blue + ow) = b » 1; *(c_red + ow) » r » 1; (c_green + ow) = g » 1; (c_itsty + ow) = 1 » 1;

-

vramdotset( ow, 0x1, 0x80, kpb, kpr, kpg, kpl, 1, 1, 0, 0 ); kpb = b; kpr = r; kpg = g; kpl = 1; }

zoombitdt(); dotcrson(); }

/* 編集パターン を左方向へ 1 ドッ ト 移動する */ void dotshiftl(void) (

int i, e, iO, iw, j, 〇〇, ow; unsigned char b, r, g, 1, kpb, kpr, kpg, kpl; dotcrsoff(); e = xmax /8-1; for(j = 0, iO = fukugen, 〇〇 = wwindow; j < ymax; ++j, 〇〇 += 80, iO += 80) { kpb = *(c_blue + iO); kpr = *(c_red + iO);

G.V. RAM 上のパターンのコピー 101 kpg *(c_green + iO); kpl = *(c_itsty + iO); iO + e, ow for(i = 0, iw ow) { ++i, 一-iw, b = *(jblue + iw); r *(c_red + iw); g == *(c green + iw); 1 *(c_itsty + iw);

---

-

*(c_blue *(c red *(c_green *(c_itsty

« « = g« =1«

»

〇〇

+ ow) = b r

1; 1;

+

1;

+ ow) ow)

-

+ e;i < xmax / 8;

1; + ow) vramdotset( ow, 0x80, 0x01, kpb, kpr,

--

kpg, kpl, 1, 1, 0, 0 );

b; kpb r; kpr kpg =• g; kpl « i;



} } zoombitdt(); dotcrson(); }

/* 編集パターン を 上下 に 1 ドッ ト 移動する */ void dotshiftud( int k) int 土, j, 1, m;

dotcrsoff(); if(k == 0) { j-80; = wwindow + ymax * 80 m fukugen;

1

- 80;

} else { j—80; 1 = wwindow; 80; m fukugen + ymax * 80 } abchange(fukugen, wwindow); abchange(wwindow, fukugen + j);

-

-


0) printf ('• ¥b");



} else break; }

if(name[i] == Oxlb) { printf(M M); j = 1; break;

}

if(name[i] == Oxd) { printf("¥n"); name[i] = 0; break;

} }

if(j != 1) ( 0; i < 40; ++i) { for(i if(name[i] == 0) break; 1 if(name[i] * * I I name[i] j = 2;

-

~

=-

* ? *) {

グラフィック画面のセーブ/ロード 111 break; )

}

print f ("Vninput file name

= %s¥n",

name) ;

}

return ( j) ; }

/* フ ァイ ルの表示 */ void file disp (char name [ ] ) {

int i; struct ffblk f fblk; if (findfirst (name, &ffblk, 0) whiled)

0)

{

(

.

printf ("¥n*s", ffblk f f_name) ;

if (findnext (&ffblk) ! = 0) break; printf ("¥t%s", ffblk . f f_name) ;

if (findnext (&ffblk) !=0) break; } }

}

/*

グラフィック 画面 データ を ディ スク からロードする

*/

gload(void) { char fname [ 64]; FILE *sfp;

int c, i, j, k, 1, s, done, f sin; struct ffblk ffblk; KEY_INFO key inf; f sin

=

0;

whiled)

i

=

{

3;

while (i) {

printf ("¥n¥ninput file name¥n") ;



= get name (fname) ; 1) { if (i

i

f sin break;

2;

112 第 3 章 パターンエディタに挑戦 if(i 2) file_disp(fname); else i = 0; }

if(fsin == 2) break; printf("load file name

-

%s ", fname);

sfp = fopen(fname, "rb"); sfp) { if(NULL 3; fsin

-

break;

} keyinf.cmmd -3; bios98key(&keyinf); mwindow; i < 400; ++i, l+»80) { for(i = 0, 1

- mwindow); ++j, ++k) {

for(j = 0, k = 1; j < (80 1; fsin if((c «= fgetc(sfp)) « *(c_blue + k) » c; if((c = fgetc(sfp)) == *(c_red + k)-c; if((c = fgetc(sfp)) == *(c_green + k) = c; fgetc(sfp)) if((c *(c_itsty + k) = c;

-

fsin

=

EOF) break; EOF) break;

~ EOF) break;

0;

1) break; if(fsin 2; keyinf.cmmd if(bios98key(&keyinf)

-

fclose(sfp); break; } keyinf.cmmd = 3; bios98key(&keyinf); fmes(fsin, fname); return(fsin);

}

EOF) break;

&

P_SHIFT) break;

ファイル名の入力 113

ファイル名の入力

さて、ファイルのセーブ/ロード の手順 は、それぞれ TURBOC の関数

を使うだけですから問題あ り ませんが、LIST3-16.C で問題 とな る の は、

ファイル名の入力方法でしょう。人間に は ミス がつ きものですし、人間は、 わが ま ま ですから入力の途中で中断したい場合 も ある からです。

そこで、 LIST3-16.C では比較的自由度 を持たせたファ イ ル名入力専門 の関数 getname を用意 ま した。特徴 は、一文字ずつ キー情報を取り込んで

引数の配列 name口へ格納してい る ことです。 というのも、[ESC]キーが

入力さ れた場合に は速やかに処理を中断す る ようにしたいからです。ま た、 配列ですから[BS]キーが入力さ れた時に は インデックス を単純に 一1する ことによって以前のキー入力値を取り消すことができる とい う メリット も



あります。さらに、ワイルド カード の入力 も チェックできる ように、リタ

ン情報をセッ ト してい ます。なお、LIST3-17.C は キーボード 割り込みを コ ントロールする ため の ものです。

LIST3-17.C ♦include # include

#include

♦include

extern termkey(void); extern initkey(void); extern dispwaku(void); extern void initcolor(void); extern void dotcrsoff(void); extern void dotcrson(void); extern void vramdotset( int, int, char, int, int, int, int, int, int, int, int ); extern int ex,

114 第 3 章 パターン エディタに挑戦 ey;

void sideclr(int );

/* ファイル操作初期設定 */ void fstart(void) { clrscr(); dotcrsoff(); sideclr(399); termkey();

textcursor(DISP_CURSOR);

/* ファイル操作終了時処理 */ void fend(void) { dispwaku(); initcolor(); dotcrson(); textcursor(NODISP_CURSOR); initkey(); outportb(0x64, 0);

/* 左側 画面の消去 */ void sideclr(int y) { vramdotset(0, 0, Oxff, 1, 2, 4, 8, 36, y, 0, 0); } /* 編集エリアのサイス 変更用 */ void change box(void) { fstart(); ey ex = 0; initset(); fend();

=

0;

ディレク ト リ内のファイルの探索 115

ディレクトリ内のファイルの探索

ファイル名に ワイルド カード が入力された場合に は、指定したワイルド カード に従って画面上にファイル を表示させなければなり ません。 とい う わけで、この処理を実現してい る のが file_disp() とい う関数です。この関

数の流れ は、次の ように なっています。

1. 関数 findfirst で、初め に一致す る ファイル名 (ワイルド カード を 含む) を探索し、存在すれば画面に そのファイル名をプリント す る。

2 . 関数 findnext で、次に一致す る ファイル名を探索する。一致する ファイ ルが存在すればプリ ントし再び次 に一致す るフ アイル名を

探索する。 もし、一致する ファイルが存在しなければ処理を終了 とする。

ここでの ポイント は、ファイル名の入力と、 ディレクトリ 内のフ ァイル 表示、ファイル ポイ ンタによ る ファイル の入出力管理 の3つ にしぼられ ま

す。この3つ さえ 理解できれば LIST3-16.C は卒業です。では実際に これら を組み込んだプロジェクト

LIST3-18.PRJ を実行して みましよ う。なお、

グラフィックデータをロード する時の中断 は[SHIFT]キー としました。

LIST3-18.C # include

#define MAKE_CR

0x39

#def ine MAKE_CL

0x38 0x62 0x63 0x64 0x65 0x66 0x70 0x29

♦define MAKE_F1

#def ine MAKE_F2 #def ine MAKE_F3

♦define MAKE_F4

#define MAKE_F5 #define MAKE_SHIFT #def ine MAKE_ZOOM

116 第 3 章

パターンエディタに挑戦

♦define MAKE_ ROLL

0x13 0x3c 一 ♦define MAKE _LEFT 0x3b 一 ♦define MAKE _up 0x3a 一 ♦define MAKE _DOWN 0x3d 一 ♦define MAKE _HANTEN 0x16 一 ♦define MAKE__LOAD 0x25 ♦define MAKE SAVE Oxle 一 ♦define MAKE.CHANGE 0x2b 一 ♦define MAKE _RIGHT

extern void grphinit(void); extern void pointer set(void); extern void zoombitdt(void); extern void dispwaku(void);

extern void initset(void); extern void dotcsr(int); extern void initcsr(void); extern void termcsr(void); extern void initkey(void); extern void termkey(void); extern void rcolor(void); extern void ェcoェor(void); extern void initcolor(void); extern void allpaint(int, int, char, extern void ptn copy(void); extern void rolling(void); extern void dotshiftr(void); extern void dotshiftェ(void); extern void dotshiftud(int); extern void udhanten(void); extern void gsave(void); extern void gload(void); extern void fstart(void); extern void fend(void); extern void change_box(void); extern int

wwindow. color, keydat space;

,

ディレクトリ内のファイルの探索 117

int xmax = 64, ymax = 64, 0, xmin ymin = 0; void main() (

grphinit(); pointer_set(); initset(); dispwaku(); zoombitdt(); initcsr(); initkey(); initcolor(); keydat » 1; while(keydat !■ 0) { switch(keydat) { case MAKE_CR: rcolor(); break; case MAKE_CL: Icolor(); break;

case MAKE_F1: allpaint(wwindow, color, Oxff,

0);

break;

case MAKE_F2: allpaint(wwindow, color, Oxaa, break; case MAKE_F3: allpaint(wwindow, color, 0x55,

1);

1);

break;

case MAKE_F4: allpaint(wwindow, color, Oxaa, 0); break; case MAKE F5: allpaint(wwindow, color, 0x55, 0); break;

case MAKE SHIFT: ptn_copy(); break;

118 第 3 章 パターンエディタに挑戦 case MAKE_ZOOM:

zoombitdt(); break;

case MAKE_ROLL: rolling(); break;

case MAKE_RIGHT: dotshiftr(); break;

'

case MAKE_LEFT: dotshiftl(); break;

case MAKE_UP: dotshiftud(0); break;

case MAKE_DOWN: dotshiftud(l); break;

case MAKE_HANTEN: udhanten(); break;

case MAKE_LOAD: fstart(); gloadO ; fend(); break;

case MAKE_SAVE: fstart();

gsave(); fend(); break; case MAKE_CHANGE: change_box(); break;

default : break; }

}

termkey(); termcsr(); closegraph(); }

ディレクトリ内のファイルの探索 119

LIST3-18.PRJ list2-0 list2-l list3-0 list3-2 list3-4 list3-6 list3-8 list3-10 list3-12 list3-14 list3-16 list3-17 list3-18

本書では サンプルプログラム とい う ことで機能的に は、 かなり 省いた も のとなってしまいました。

しかし、これ だけ の機能があれば、本書で作成する ゲーム では十分 とい える でしょう。最後に、パターンエディタの締め く く り として2章で作成し

た、 マウス によ る、お絵描き ツール とドッキングさせて みましょう。当然

のこ とながら、マウス とマウスドライ バ (mouse. sys) が必要 となって き ま す。では、メイン関数 とプロジェクト ファイル を打ち込んで起動して みて

ください。

LIST3-19.C #include #include # define MAKE_CR #define MAKE_CL

0x39 0x38 0x62 #define MAKE_F1 #define MAKE_F2 0x63 #define MAKE_F3 0x64 #define MAKE_F4 0x65 #define MAKE_F5 0x66 #define MAKE_SHIFT 0x70 0x29 #define MAKE_ZOOM #define MAKE_ROLL 0x13 #define MAKE_RIGHT 0x3c

120 第 3 章 パターンエディタに挑戦 #define MAKE_LEFT

0x3b 0x3a ♦define MAKE_DOWN 0x3d #define MAKE_HANTEN 0x16 ♦define MAKE_LOAD 0x25 ♦define MAKE_SAVE Oxle ♦define MAKE CHANGE 0x2b ♦define MAKE UP

extern int wwindow, mwindow, color, keydat, memory adr, msonoff, bit_adrf csr on, space; extern extern extern extern

extern extern extern

extern extern extern extern extern extern extern extern

extern extern extern

extern extern

extern extern extern

void grphinit(void); void pointer set(void);

void void void void void void void void void void void void void void void void void void void int void

zoombitdt(void); dispwaku(void); initset(void); dotcsr(int); initcsr(void); termcsr(void); initkey(void); termkey(void); rcolor(void); Icolor(void); initcolor(void); allpaint(int, int, char, int); ptn copy(void); rolling(void); dotshiftr(void); dotshiftl(void); dotshiftud(int); udhanten(void); dot_plot(int); xycset(int, int, int, int); cursorxor(int, int, int);

ディレクトリ内のファイルの探索 121

extern void gsave(void); extern void gload(void); extern void fstart(void); extern void fend(void); extern void change_box(void); extern void file__disp(void); extern void mouseinit( unsigned int, unsigned int, unsigned int, unsigned int); extern void mouseterm(void); extern void mousexyset(int, int);

int xmax ymax xmin ymin



=

-

64, 64, 0,

0;

void main() {

int sfsin;

grphinit(); pointer_set();

initset(); mouseinit(mwindow * 8, 639, 0,

cursorxor(memory_adr,

399);

bit_adr, color);

dispwaku(); zoombitdt(); initcsr(); initkey(); initcolor(); keydat « 1; while(keydat != 0) { switch(keydat) { case MAKE_CR: disable();

if(csr_on



1)

cursorxor(memory_adr, bit_adr,

color);

msonoff 0; enable(); rcolor();

cursorxor(memory_adr, bit_adr, color); msonoff =1;

122 第 3 章 パターンエディタに挑戦 break;

case MAKE CL: disable(); 1) if(csr on cursorxor(memory_adr, bit_adr, color); msonoff = 0; enable(); Icolor();

cursorxor(memory_adr, bit_adr, color); msonoff

=

1;

break;

case MAKE Fl: allpaint(wwindow. color. Oxff,

0);

break;

case MAKE F2: allpaint(wwindow, color. Oxaa, 1); break;

case MAKE F3: allpaint(wwindow, color, 0x55, 1); break;

case MAKE F4: allpaint(wwindow, color, Oxaa, 0); break;

case MAKE F5: allpaint(wwindow, color, 0x55, 0); break;

case MAKE SHIFT: disable();

if(csr on == 1) cursorxor(memory_adr, bit_adr, color); msonoff

= 0;

enable(); ptn_copy();

msonoff = 1; break;

case MAKE ZOOM: zoombitdt(); break;

case MAKE ROLL: rolling(); break;

case MAKE RIGHT:

ディレクトリ内のファイルの探索 123

dotshiftr(); break;

case MAKE_LEFT:

dotshiftl(); break;

case MAKE_UP: dotshiftud(O);

break;

case MAKE_DOWN: dotshiftud(l); break;

case MAKE_HANTEN: udhanten(); break;

case MAKE_LOAD: disable(); if(csr_on == 1) cursorxor(memory_adr, bit_adrr color); msonoff = 0; enable(); fstart(); gload(); fend(); msonoff = 1; break; case MAKE_SAVE: disable(); if(csr_on == 1) cursorxor(memory_adr, bit_adr, color); msonoff = 0; enable(); fstart(); gsave(); fend(); msonoff

= 1;

break; case MAKE_CHANGE: change_box(); break;

default : break; }

.

124 第 3 章 パターンエディタに挑戦 }

mousetermO ; termkey () ;

termcsr () ; closegraph () ; }

LIST3-19.PRJ l±st2-0 list2-l list2-6 list2-9 list2-ll list3-0 list3-2 list3-4 list3-6 list3-8 list3-10 list3-12 list3-14 list3-16 list3-17 list3-19

第4章

TV ゲーム の世界

グラフ ィ ック ス 処理 の ひとつ の分野 として発展してき た TV ゲーム で すが、一口に ゲーム といって も、迷路型、 シューティング タイプ、ロール

プレイ ングなど様々な形態があ り ます。中でも リ アルタイム ゲーム の分野

は、プログラム の結果を確かめながら進める ことができる ため に、プログ ラミング も 楽しく 進めていく こと が できます。本章では3章 のパターンエ

ディ タでデザイ ンしたキャラクタを使って、 実際に アニメーション処理を、

さらに、 これを発展させたリ アルタイム ゲーム を作っていきます。 まず話を進める前に、 アニメーション用のロ絵パターン を用意しておき

まし ょ う。デザイ ンしたパターン は、ひとつ のファイル としてカレント ディ レクトリ へ格納しておいて ください。プログラム を効果的に、 また楽しく

進めていくため に も、 この ような地味な作業が必要 となって きます。 もち ろ ん、 パターン数さえ揃っていればオリ ジナル のデザイ ンでも なんら差し

支えありません。なお、各 キャラクタの大きさ は32X32ドッ ト としました。

126 第 4 章 TV ゲームの世界_



アニメーション処理

アニメ

ーション処理の実際 は、説明する までも な く 徐々 に動いてい く 過

程を連続して表示すれば実現できる わけです。対象 とな る 動作をいかに細 かく分解する かに よって、動き のなめらかさが決まる のです。 もっ とも、

メモリ に制限のある パソコンでは、見せ場とな る パターンにより多く の動

作パターン を割り付ける とい う手法を と り ます。

一枚の絵となって

さて、デザインしたパターン を実際に利用する に は、

いる 画面上の絵を適当な大きさ に切り取ってメモリ へ と格納しておかなけ

ればなり ません。TURBO C に は画面上の指定された範囲のビッ ト イメー ジ を メモリ へ セーブ する

getimageO とい う 関数 と、 セーブしたビット イ

メージ を画面上に復元する putimageO とい う 関数があ り ます。 これら の

関数を利用する の も ひとつ の方法 としてあげられます。 しかし、TV ゲーム などでは画面上のビット イメージ を 単純に セーブし

て その まま 復元する より も、 これら のビッ ト イメージ をさらに加工する場

合がしばしばですから、構築する システム に合わせた独自の関数を作る必 要があります。そこで、これまでの応用 も 兼ねて、画面上のビット イメー ジ を メモリ へ セーブする 関数 と、 画面に絵を復元する関数 を作る ことにし

ました。

メモリ と は、 すなわち変数のこ とですが、ここで画面上のデータを変数

図4T

パターンのデータ構造

アニメーション処理 127

へ と格納する こと を考えて みます。口絵のデザイ ンではひとつ のパターン の大きさ を32X32ドッ ト しましたから、この場合のデータ構造 は図4-1の よ

うに なり ます。 まず X 方向です、これ は汎用性を考慮すれば char 型の変数配列を使い

たい所ですが、 スピード 最優先のリアルタイム ゲーム という ことで、デー タを まとめて処理できる 32ビッ ト 長の long 型の変数を使う ことにします。

ですから、X 方向は long 型の変数ひとつで対応できる わ けです。次 に

Y方

向ですカマ、これ は単純に long 型の変数が32ライ ン重なる こ と に なります。 また、プレーン は4つあり ます から、ひとつ のパターン を構成する変数域 は

次 の ように、簡単な構造体として定義できる ことに なります。 32X32ドッ ト で構成 する ひとつのパターン を 格納する ため の構造体定義 #define PTY 32 struct {

long pb[PTY]; long pr[PTY]; long pg(PTY]; long pi[PTY]; } pattern;

/* Bプレーン 用データエリア /* Rプレーン用データエリア /* Gプレーン 用データエリア /* エ プレーン 用データエリア

*/ */ */

*/

また、実際に は パターン数が ひとつではありませんから、この構造体自

体を配列としてパター ン数分確保す る ことにします。

32X32ドッ ト で構成 する 50個のパターン を 格納 する ため の構造体定義 ♦define PTY 32

struct { long pb[PTY]; long pr[PTY]; long pg[PTY]; long pi[PTY]; pattern[50];

/* /* /* /* /*

Bプレーン用 データエリ ア Rプレーン用 データエリ ア Gプレーン 用データエリ ア エプレーン 用データエリ ア パターン 数確保する */

*/ */ */ */

ここでは、50個のパターンを格納できる 構造体を定義しましたかヌ、 この 数 は適当に設定して ください。 さて、このよ う に変数の構造が確定でき れば、あ と は単純に画面上のデー

128 第 4 章 TV ゲーム の世界 タを取り込む だけです。早速、ビッ ト イメージ を メモリ へセーブする pat-

tern Jn() とい う 関数 と、処理 として は逆 のビット イメージ を画面上に復 元する pattern _out()とい う関数を作って みましょう。

LIST4-0.C

#include #include ♦include # include

extern char far *c blue; extern char far *c red; extern char far *c_ green; extern char far *c_itsty; ♦define PTY 32 /* パターン 格納用構造体の定義 */ struct { long dtb[PTY]; long dtr[PTY]; long dtg[PTY]; long dti[PTY]; } pattern[50]; /* 画面上データ を メモリ へ セーブする */ pattern in(unsigned int i, int j) {

int k; if(i < 50) { 0; k < PTY; j+=80, ++k) { for(k pattern[i].dtb[k] = *(long far pattern[i].dtr[k] = *(long far pattern[i] ,dtg[k] = *(long far pattern[i].dti[k] = *(long far

-

j

i

=

0;

}

i = 1; else return (i); }

*)(c_blue

+ j);

+ j); + j); *)(c_itsty + j);

*)(c_red

*)(c_green

アニメーション処理 129

/* メモリ から 画面へバ ターン を 復元する */ pattern out(unsigned int i, int j) {

int k; if(i < 50) { for(k = 0; k < *(long far *(long far *(long far *(long far

i }

-

PTY; j+=80, ++k) {

*)(c_blue *)(c_red

+ 3) = pattern[i].dtb[k]; + 3) = pattern[i].dtr[k];

*)(c_green + 3) *)(c_itsty + j)

= pattern[i].dtg[k]; = pattern[i].dti[k];

0;

-

1; i return (i);

else

これら関数の引数に は、 パターン N0•と32X32ドッ ト で構成する

BOX

型の左上の G.V.RAM アドレス を与える こ と としました。なお、パターン

数の最大値である 50 を超えた場合に は、 なに も 処理せず に関数の戻り値 と して1を返す ようにしてあります。また、パターン N0.を示す引数 i の定義 では unsigned としている ことに注意して ください。このことに より 配列 を 参照 する 時の i の範囲 は03i j) i k; } while(getch() != Oxlb);

-

} else { printf("¥n press any key"); getch(); }

closegraph();

LIST4-1.PRJ list2-0 list2-l list3-16 list4-0 list4-l

132 第 4 蔓 TV ゲーム の世界

LIST4-1.C を実行す る と、パター ンが格納してある ファイ ル名の入力を 促すメッセージが現れます。ファイル名を指定すると、次 に アニメーショ

[SPACE]キー ンの スタート N0.とエンド N0.を入力します。コマ送り は



としました。では、口絵のパターンで実験して みましょう。 アニメ ショ ンの スタート N0.として7をエンド N0.として18を 入力して く ださい。

[SPACE]キー を押し続ける と三角形の板が回転してい る 状態が表示され る はずです。この例ではパターン の大きさ を32X32ドッ ト としましたから

迫力に欠けますが、 もっと大きなパターン用の関数を作って試して みる の も おもしろいでしょう。 また、 セーブしたパターン を斜め方向に復元した

り、構成する色を変えたり 等、セーブしたビット 情報を色々 と加工して み る の も よい でしょ う。



マップエディタに挑戦 133

マップエディタに挑戦

さて、 ここで LIST4-0.C のおもしろい 応用を 考えて みま しょう。 それ

は、CRT 画面の中に2次元の世界を作り出すため のマップエディ タとい う

ツール です。マップエディ タ とい うの は パターン エデ ィ タ と 並んで TV ゲーム に は欠かせない ツール となり ます。しかも、LIST4-0.C を使えば簡

単に作る こ とができる のです。 さて、 複雑なマップを作る に は それなり に 複雑な処理 となり ますが、 こ

こでは移動方向は一方向のみ とい う制限を付ける ことにします。 マップの基本パターン は他のパターン と同様に32X32ドッ ト とすると、

画面横方向に は20個のパターンを並べる こ とができますから、マップデー タ は パター ンが横に20個並んでお り、それが縦方向に 展開する構造 とし、

各パターン は番号で管理す る ものとします。ですからマップデー タを管理 する為の変数は、次の よ う に単純な2次元配列とすれば よいでしょ う。 #define PYMAX 240-1 #define PXMAX 20-1 unsigned char map data[PYMAX+1][PXMAX+1];

まず、マップエディタを作る 前に、 ここで定義したデータを ディ スクに セーブしたりロード する為の専用の関数を作る ことにします。

134 第 4 章 TV ゲームの世界 /* マップデータ格納用変数配列 の定義 */ unsigned char map_data [PYMAX+1] [PXMAX+1];

/* マップデータを ディ スク へセーブする */ map_data save (void) { char f name [64] ; FILE *sfp; char c;

int

'i,

j, k, 1, fsin;

vramdotset (0, 0, Oxff, 1, 2, 4, 8, 36, 400, 0, 0) ;

=

f sin

0;

whiled) {

i

-

3;

while (i) { printf ("¥n¥ninput save file name¥n") ;

i

®

getname (fname) ;

if (i



1)

f sin

=

{

2;

break; }

if (i == 2) file_disp (fname) ; else i

0;

}

if (f sin “ 2) break; if (access (fname, 0)

0)

{

printf ( "¥n%s is allready exist ¥ncontinue (Y-N) ? ",

fname ) ;

while (1) { c = getch () ; if (c == *n 1 || c f sin

=

•N1 )

(

2;

break; }

if (c

==

*Y' ||c

==

Y) {

printf ("Y") ; break; }

}

if (f sin

==

2) break;

}

else

printf ("save file name

= %s

", fname);

マップエディタに挑戦 135

-

f open (fname, nwb") ; if (NULL -= sfp) {

sfp

-

fsin

3;

break;

}

=

fsin

0;

-

for (i

XMAX) {

-

X

=

XMIN;

y += 1; if(y > YMAX) y == YMAX; J

boxcursol(y *32*80+x*4+ mwindow); pattern_out(x + y * (XMAX +1), 0); IJ

else { boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow); ++x2;

if(x2 > XMAX2) x2 = XMIN2; x2; px boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow); }

/* ボックス カーソル左方向への移動 */ void left_set(void) { KEY_INFO keyinf; keyinf.cmmd = 2; if(bios98key(&keyinf) !» 0) { boxcursol(y *32*80+x*4+ mwindow);

x



1;

if(x < XMIN) { x = XMAX;

マップエディタに挑戦 141 y



if(y

1;
YMAX) y boxcursol(y *32*80+x*4+ mwindow); 二

pattern_out(x + y * (XMAX + 1), 0);

} else {

boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow); ++y2; if(y2 > YMAX2) { y2; pyO; if(pyO < 0) pyO = 0; else disp map(pyO); } py; if(py < 0) py 0; boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow);

-―



I

-

142 第 4 章 TV ゲーム の世界 /* ボックス カーソル上方向への 移動 */ void up set(void) { KEY_INFO keyinf; keyinf.cmmd = 2; if(bios98key(&keyinf) != 0) { boxcursol(y *32*80+x*4+ mwindow);

--

y; YMAX; if(y < YMIN) y boxcursol(y *32*80+x*4+ mwindow); pattern_out(x + y * (XMAX +1), 0);

-

} else {

boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow); y2; if(y2 < YMIN2) { ++y2; ++py0; 5; PYMAX 5) pyO if(pyO > PYMAX else disp map(pyO);

--

-

-

}

++py; PYMAX; if(py > PYMAX) py boxcursol(y2 * 32 * 80 + x2 * 4 + mpwindow);

-

/* マッ プの表示 */ void disp map(int pyO) { int i, j, k, 1; for(i 0, k = 0x7d00 32 * 80; i < 6; ++i) ( for(j = 0, 1 = k; j

このよ う に上位ニブル は7、下位ニブル は GDC 内部 の RAM アドレス

(以後 RA と略記) とな る のがスクロール コマンド です。RA の初期値 は0 と なり ます から、初め に OUT する スクロールコマンド は0x70 となり ます。

また、 RA は自動的に インクリメント (

+ 1) される ので、コマンド は一度

送る だけです みます。コマンド のあと は、必要 とな る パラメータを送り ま

す。図4-4を参照して ください。

図4-4 表示画面との対応.

148 第 4 章 TV ゲームの世界

AREA2に二分割して、それぞれ SL2 とすると、各パ SL1と、

このよ う に ひとつ の画面を AREA1と の開始アドレス と ライン数 を

ラメータは表4-3の よ う な順番で送 る ことに なります。

表4-3 スクロールコマンドの各パラメータ



DAD+2



一 によ る インクリメント

0

、、r

1

、、 2" によ る インクリメント (DAD+2fDAD)

(DAD+lIDAD)

DAD:Display Address

IM

表示 制御

L/R 制 御

0

2 クロック に 1回表示ア ドレス を インクリメント

CSRFORMコマン ド の既定値使用

1

4 クロック に 1回表示 ア ドレス を インクリメント

L/R=Oに強制

IM:lmage

ところで、最近の機種では GDC の動作クロックが5MHz と 2.5MHz の

二通り があ り、ュー ザーが選択して使用できる ように なっていますから、

GDC によ る スムーズ スクロール 149

動作クロックについて も 考慮しなければなり ません。 どちらの クロック を

使用 する か は ディップ スイッチ2番 の8によ って 選択します (OFF で2. 5

ON で5MH

なお、変更に際して はシステム を再起動しなければ

なりません。

当然 のこ とながら GDC のパラメータ もこ の動作クロック に合わせる 必

5MHz 要があ り ます。スクロールコマンド では、2. 5MHz で IM ビット =0、 で IM ビット =1となり ます。では以上のこ と を踏まえて次のプログラム へ と 進んでください。

LIST4-4.C /* GDCによ る スムーズ スクロール

*/ ♦define PTY

32

♦define SCLDOT 2 ♦define SCLD16 SCLDOT*16 ♦define SCLPTC SCLDOT*40

#define GSTADR 0x4000 ♦define GENADR 0x7e80

♦define STLINE 0x1900 ♦define ENLINE 0xl900-SCLD16 ♦define VEND

♦define PYMAX

400*80 240-1

♦define PXMAX

20-1

extern char far *c_blue; extern char far *c_red; extern char far *c_green; extern char far *c itsty; extern struct { long dtb[PTY]; long dtr[PTY];

long dtg[PTY]; long dti[PTY]; } pattern[50]; extern unsigned char map data[PYMAX+1][PYMAX+1];

int enemy tablel[20]; int enemy sign;

150 第 4 章 TV ゲーム の世界 char gdchz

= 0;

void scroll(int gvs, int gve, int sign) {

Struct BYTE { char low; char high;

union' DATA { short dataw; struct BYTE datab;

union DATA

nol, no2, adl, ad2;

static int scladl, sclnol,

sclno2, map_ptn_y,

map_y, map_x;

int 1, m, n, p, q; /* sign=0ならば全画面 を クリア する */

if(sign == 0) ( map_ptn_y

=

map_x

= 0 ;

map__y

= 0;

PTY-SCLDOT;

SCladl = GSTADR + for(1 = gve, m = 0; m < VEND / if(1 >= VEND) 1-=VEND; *(long far *)(c_blue + 1) *(long far *)(c_red + 1) *(long far *)(c_green + 1) *(long far *)(c_itsty + 1)

4; ++m, l+=4) {

= 0; = 0; = 0;

=

0;

}

/* sign=lならば不要部分の消去 を する */ gve; m < 20; ++m, p+=4) { for(l =0, q = p; 1 < SCLDOT; ++1, q+=80) { *(long far *)(c_blue + q) = 0;

else ( for(m

=

0, p

=

GDC によ る スムーズ スクロール

151

+ q) •M *(long far *)(c_green + q) • 0; *(long far *)(c_itsty + q) • 0;

*(long far *)(c_red

}

--

) } while((inportb(OxaO) & 0x4) == 0); outportb(0x00a2, 0x70); /* スクロール コマンドの送出 */ scladl SCLPTC; if(scladl m Oxaaaa;



-

--



152 第 4 章 TV ゲーム の世界 } /* signalならばマップデータを 表示する */ else { for(map_x 0; map x < 20; ++map_x, gvs+=4, gve+-4) { n = map_data[map y][map x]; for( 1 = 0, m = map_ptn_y, p = gvs, q = gve; 1 < SCLDOT; ++1, ++m, p +■ 80, q += 80 ) { *(long far *)(c_blue + p) = pattern[n].dtb[m]; + p) = pattern[n].dtr[m]; *(long far *)(c_red pattern[n].dtg[m]; *(long far *)(c_green + p) *(long far *)(c_itsty + p) = pattern[n].dti[m]; } } map_ptn_y -= SCLDOT; if(map_ptn__y < 0) { SCLDOT; map_ptn__y = PTY ++map y; if(map_data[map_y][0] == 0 I I map_y >■ 240) map y » 0; enemy_sign gvs 80 * 2; for(n = 0; n < 20; ++n) { enemy_tablel[n] = map_data[map y][n];

-

-

-

-

-

}

} else enemy sign = 0; } }

さて、 LIST4-4.C では、 スクロール と共に マップの表示処理 も 同時に

行っています。マップの表示開始位置 を示して いる の が 関数 の引数 gvs で、表示終了位置を示している のが gve です。すなわち、gvs の位置でス ク ロールした分だけマップを表示し、gve の位置で不要部分の消去を する わ

けです。

引数の sign は画面の初期状態 を表示する ため の サイ ンを 意味してい ま す。sign=0で初期画面の表示及び使用変数の初期化作業をしてい ます。で は、 この関数 を 実際に 動かして みましょう。main() 関数 と プロジェク ト

ファイル は次の ように なります。 なお、プログラム の先頭では使用する パ ターン を格納してある ファイル とマップデータを格納してあるファイル を

ロード する 構造 としました。

GDC によ る スムーズ スクロール 153

LIST4-5.C /* スクロール の テス ト

*/ ♦include

#include #include #include #include ♦define PTY

32

♦define NPAT

80*PTY

♦define MAKE_ESC

♦define PTY0

0 2 42*80*8

♦define VSTADR

16

♦define SCLDOT

♦define MAKE RETURN Oxlc

extern int extern int extern int

pattern_in(unsigned int, int); pattern_out(unsigned int, int); gload(void);

extern int map_data_load(void); extern void scroll(int, int, int); extern char gdchz;

int

mwindow

=

36;

void main() (

int i, j, k, 1, m, n, pyO, pye, keydat; KEY_INFO keyinf;

if(inportb(0x31) & 0x80) gdchz = 0; else gdchz = 0x40; grphinit(); pointer_set(); printf("¥nハ•ターン テ’ータ ロート' •,);

if(gloadO == 0) { for(k = m = n = 0, 1 = mwindow; k < 4; ++k, 1+=NPAT) { for(i = 0, j = 1; i < 11; ++i, j+=4) { n = pattern_in(m++, j);

154 第 4 章 TV ゲームの世界 if(n !- 0) break; J

if(n != 0) break; J

map_data_load(); clrscr(); keyinf.cmmd = 1; keydat = 0; pyO = 80 * VSTADR; pye = 400 * 80 80 * SCLDOT; scroll(80 * VSTADR, pyO, 0); 0) { while(keydat keydat = bios98key(&keyinf); scroll(pyO, pye, 1); pyO -= 80 * SCLDOT; 80 * SCLDOT; if(pyO < 0) pyO = 80 * 400 pye -= 80*SCLDOT; if(pye < 0) pye = 80 * 400 - 80 *

-

-

}

} else { printf("¥n press any key"); getch(); i

closegraph(); }

LIST4-5.PRJ list2-0 list2-l list3-0 list4-0 list4-2 list4-4 list4-5

ヒー ロ ー登場 155

ヒーロー登場

いかがでしたか、実際に デザイ ンしたマ ッ プがス クロールしてい く 過程

を見る の は一種感動する ものがあ る のではないでしょう かへ さて、いよ い

よ 作り出した新しい世界の中を動き回る ヒーロー の登場 と 願いましょう。 ある パターン をグラフィック画面上に表示する場合、パターン の余白か

ら見える はずの背景をいかに表示す る かという重ね合わせ処理が問題 と な

り ます。 これを完全に処理する ため に は、 どの部分がパターンで、 どこ からが余

白である のかを表す専用のデータが必要 となり ます。 データ構造として は パターンがあ る所は0、無い所 は1として背景との論理演算 AND を実行す

る ことで背景を パターン の形でく り 抜く とい う操作を する わけです。 後 は、

く り 抜いた背景 の所に論理演算 の OR でパターン を表示すれば よい ので

す。

実は、 このよ うに言葉で説明する の は簡単な のですが、これをプログ ラ ム すると なると、 かなり 複雑に なってしまいます。 そこで本書では、 この

重ね合わせ の問題を解決する ため に、砂漠 (口絵6) 以外に は移動できない とい う制限を付ける ことにします。 すなわち、パターン の余白の部分にあ

らかじ め背景の砂漠のパターンで埋めておく わけです。この ような制限を

付ける ことに よってプログラム は飛躍的に簡単に なります。しかも、パター ンの移動後に残る残骸を元の背景に復元する処理 も 単純に砂漠を描けば よ

いわけですから、さらに処理が簡単に なる わけです。 もっとも、マップを

描く時に は必ず砂漠が存在しない と移動できない とい う ことに なりますか ら注意して ください。では、LIST4-6.C へ と進んでください。

LIST4-6.C #def ine PTY #def ine NPAT #define MAKE_RIGHT

32 80*PTY 0x48

156 第 4 章 TV ゲーム の世界 #define MAKE_LEFT

0x46

♦define BREAK_RIGHT' 0xc8 ♦define BREAK_LEFT

♦define XMAX ♦define XMIN ♦define ON ♦define OFF ♦define CRASH ♦define CONTINUE

♦define UE ♦define MIGI ♦define HIDARI ♦define VEND

0xc6 76 0 1 0 1 0 1

2 3 400*8

extern char far *c_ blue; extern char far *c_ red; extern char far *c_ green;

extern char far *c_ itsty; extern int keydat;

extern struct { long dtb[PTY]; long dtr[PTY]; long

dtg[PTY];

long

dti[PTY];

} pattern[50];

int check(unsigned int, int, int);

int px = 38; /* 車の表示及び移動処理 */ int car_disp(int py, int keep_py) { static int

= OFF, dir_h = OFF, pn = 0;

dir_m

unsigned int j, k; int dir, keep_px, r;

dir = UE; keep_px

= px;

ヒーロー登場 157 switch(keydat) { case dir_m = OFF; if(dir_h == ON) keydat = MAKE_LEFT; else break; case MAKE_LEFT: dir = HIDARI; dir_h = ON; for(k =0, j = px + 3 + py; k < PTY; j+=80, ++k) { if(j >= VEND) j -= VEND; *(c_blue + j) = 0; *(c_red + j) = Oxaa; ★(c_green + j) = Oxaa; *(c itsty + j) = 0; }



px; if(px < XMIN) { dir = UE; px = XMIN; }

break; case BREAK_LEFT: dir h = OFF; if(dir_m == ON) keydat = MAKE_RIGHT; else break; case MAKE__RIGHT: dir = MIGI; dir_m = ON; for(k =0, j = px + py; k < PTY; j+=80, ++k) { if(j >= VEND) j -= VEND; 0; ★(c_blue + j) + j) Oxaa; ★(c_red *(c_green + j) Oxaa; *(c_itsty + j) }

++px; if(px > XMAX) { dir = UE; px = XMAX; }

break; default : dir_m = OFF; dir_h = OFF; break;

158 第 4 章 TV ゲーム の世界



keep_px + NPAT + py; k < 8; j+=80, ++k) { for(k 0, j VEND; if(j >- VEND) j *(long far *)(c_blue + j) = 0; *(long far *)(c_red + j) = Oxaaaaaaaa; = Oxaaaaaaaa; *(long far *)(c_green + *(long far *)(c_itsty + j) = 0; «

"

}

if(check(px + py, keep_px + keep_py, dir) == switch(pn) { case 0: 0, j = px + py; k < PTY; for(k if(j >== VEND) j 一= VEND; *(long far *)(c_blue + j) = *(long far *)(c_red + j) = *(long far *)(c_green + j) » *(long far *)(jitsty + j) = } break; case 1: for(k = 0, j = px + py; k < PTY; if(j >= VEND) j -= VEND; *(long far *)(c_blue + j) = + j) *= *(long far *)(c_red *(long far *)(c_green + j) *(long far *)(c itsty + j)

--

} break; case 2: for(k « 0, j = px + py; k < PTY; if(j >- VEND) j = VEND; *(long far *)(c_blue + j) = *(long far *)(c_red + j) = *(long far *)(c_green + j) = *(long far *)(c_itsty + j) =



} break; default : break;

} ++pn; if(pn > 2) pn r = 0; ) else ( px

=

keep__px;

-

0;

CONTINUE) {

j+-80, ++k) { pattern[0].dtb[k]; pattern[0].dtr[k]; pattern[0].dtg[k]; pattern[0].dti[k];

j+=80, ++k) {

pattern[1].dtb[k]; pattern[1].dtr[k]; pattern[1].dtg[k]; pattern[1].dti[k];

j+-80, ++k) { pattern[2].dtb[k]; pattern[2].dtr[k]; pattern[2].dtg[k]; pattern[2].dti[k];

ヒー ロ ー登場 159 r

=

1;

dir_m

=

OFF;

dir_h = OFF; keydat= 1; } return(r);

/* 移動方向が移動可能か を 判断する */ int check(unsigned int pxy, int keep_pxyr int dir)

unsigned char a, b, c; unsigned int pxy2; int 土, j, k, 1, r; long o, p, q; pxy2 = pxy; r = CONTINUE; switch(dir) { case MIGI: pxy +=3; case HIDARI: a = *(c_blue + pxy); + pxy); b = (c red c = *(c green + pxy); if(a == 0 && b == Oxaa && c == Oxaa) { pxy += 80 * 16; if(pxy >= VEND) pxy -= VEND; a = *(c_blue + pxy); + pxy); b = (c red c = *(c green + pxy); if(a == 0 && b == Oxaa && c == Oxaa) { pxy += 80*16; if(pxy > VEND) pxy -= VEND; a = (c blue + pxy); + pxy); b = *(c_red c = (c green + pxy); if(a == 0 && b == Oxaa && c == Oxaa); else r = CRASH; } else r = CRASH; } else r = CRASH; case UE:

160 第 4 章 TV ゲームの世界 *(long far *)(c_blue + pxy2); p *(long far *)(c_red + pxy2); q = *(long far *)(c green + pxy2); if(o == 0 && p == Oxaaaaaaaa && q == Oxaaaaaaaa); else r = CRASH; break; default : break; 〇 =

-

}



if(r == CRASH) { for(i ==0; i < 3; ++i) { 3; 1 5; ++1) ( for(1 printf("¥a"); while((inportb(OxaO) & 0x20) ! while((inportb(OxaO) & 0x20) for(k = 0, j keep_pxy; k VEND; if(j >= VEND) j *(long far *)(c_blue + j) + j) *(long far *)(c_red *(long far *)(c_green + j) *(long far *)(c_itsty + j)

-

0);

~ 0);

-一

< PTY; j+-80, ++k) {

= =

pattern[1].dtb[k]; pattern[1].dtr[k]; pattern[1].dtg[k]; pattern[1].dti[k];

}

}

1 = 0, j = keep_pxy; k < PTY; j+=80, ++k) { if(j >= VEND) j -= VEND; *(long far *)(c_blue + j) » 0; Oxaaaaaaaa; + j) *(long far *)(c_red *(long far *)(c_green + j) Oxaaaaaaaa; *(long far *)(c_itsty + j) = 0;

for(k

--

} return(r);

さて、 ここでの処理 の ポイント は、ヒーロー とな る 車を デザインしたパ

ーン番号の



2を順番 に表示す る 処理 の所で switch

case 文 を使用し

てい る ことです。 これ は、 構造体を参照する場合のインデックス をダイレ クト に数値を使って指定する為の工夫なのですが、この方が、変数によ る

場合よ り も 若干スピード が早い とい う ことです。 ま た、 移動可能 か否か のチェック を G.V.RAM から直接データを 読 ん

で判断してい ます。砂漠のデータ は、 B 面が0で、R 面 と G 面のデータが

ヒーロー登場 161

Oxaaaa

と並んでいる 構造 とな り ますから、車の進行方向と左右で G.

V. RAM の数値をチェックするわけです。なお、左右でチェックする場合 に は、パターン の上中下の3箇所でチェックをします。では実際に動かして

みましょう。なお、LIST4-7.C は キーボード 設定用 となり ますから注意し てください。

LIST4-7.C ♦include

♦include #define STATUS_PORT ♦define COMMAND_PORT ♦define DATA^_PORT #define EOI ♦define COMMAND #define EOIDATA ♦define ERRCODE

0x43 0x43 0x41

0 0x16 0x20 0x38

int keydat. random= 3751, 3751, seedl seed2 = 1357;

-

void frandom(void);

/* キーボード割り込み関数の定義 */ void interrupt keyset(void) {

int x; frandom(); x = inportb(STATUS_PORT)

&

ERRCODE;

if(x == 0) { OUtpOrtb(COMMAND_PORT, COMMAND) keydat = inportb(DATA_PORT); } OUtportb(E0I, EOIDATA);

} /* 疑似乱数用 関数 */ void frandom(void) { random; seedl random «= 2;

-

162 第 4 章 TV ゲームの世界 random += seedl; ++random; random = random & Oxf; random +=2;

} void interrupt (*keepvector9)(); void interrupt keyset(void);

/* キーボード 割り込み の初期設定 */ void initkey(void) {

keepvector9

= getvect(9);

setvect(9, keyset);

/* キーボード割り込み を 復元する */ void termkey(void) {

KEY_INFO keyinf; setvect(9, keepvector9); keyinf.cmmd = 3;

bios98key(&keyinf); }

LIST4-8.C ♦include

♦include ♦include ♦include ♦define PTY ♦define NPAT ♦define MAKE_ESC ♦define SCLDOT

32 80*PTY 0

♦define PTYO

2 42*80*8

♦define VSTADR

16

♦define MAKE_RETURN Oxlc

extern extern extern extern extern

int keydat, px; int pattern_in(unsigned int, int); int pattern_out(unsigned int, int); int gload(void); void initkey(void);

ヒーロー 登場 163 extern void termkey (void) ; extern int

car_disp (int, int);

extern void scroll (int, int, int); extern int

map_data_load (void) ;

extern char gdchz;

int

mwindow = 36;

void ma in ( ) {

int i, j, k, 1, m, n, py, pyO, pye, r; unsigned int kyori;

if (inportb (0x31)

& 0x80)

else

gdchz

= 0;

gdchz

=

0x40;

grphinit ( ) ;

pointer_set () ; clrscr () ;

print f .("¥n 八. ターン テ’ータ ロ••ド") ;

if (gload ( )

== 0) {

= m = n = 0, 1 = mwindow; k < 4; ++k, 1 += for (i = 0, j = 1; i < 11; ++i, j += 4) { n = pattern in(m++, コ); if (n ! = 0) break;

for (k

}

if (n != 0) break; }

map_data_load ( ) ; clrscr () ;

initkey () ; gotoxy (1, 1) ;

printf ("RALLY keydat

=

9 8 ")

1;

pyO = 80 * VSTADR; while (keydat != MAKE_ESC) {

scroll (80 * VSTADR, pyO, 0);

= PTY0; = PTY0; px = 38 ; pyO = 80 * VSTADR; pye = 400 * 80 - 80 * keydat = 0x70; i

py

SCLDOT;

NPAT)

{

164 第 4 章 TV ゲームの世界

- -

kyori

r

0x0;

0;

gotoxy(68, 1);

printf("距離"); while(keydat != MAKE_ESC && r == 0) { gotoxy(73, 1); printf(**%u m ", kyori); ++kyori;

= car disp(py, i); while(keydat == 0x70); scroll(pyO, pye, 1); r



=

—— --

py 80 * SCLDOT; if(py < 0) py 80 * 400 pyO 80 * SCLDOT; if(pyO < 0) pyO = 80 * 400 pye 80 * SCLDOT; if(pye < 0) pye = 80 * 400

-

}

termkey(); }

else {

printf(H¥n press any key**); getch(); }

closegraph();

LIST4-8.PRJ list2-0 list2-l list3-0 list4-0 list4-2 list4-4 list4~6 list4-7 list4-8

-

80 * SCLDOT;

- 80 * SCLDOT; - 80 * SCLDOT;

敵の出現 165



敵の出現

敵、すなわち、進行を邪魔 する存在ですが、本書では砂漠の中から突然、

回転しながら現れる三角形の板としました。 そうです、アニメーション の サンプルで使ったあのパターンです。アニメーション といって もパターン

番号を順番に変化させる とい う 簡単な ものです。ここで特に難しいのは出

現させる タイミングでしょう。進行を妨げる のですから、移動不可能な砂 漠以外の所に 出現したのではあまりに も 芸がないからです。次のプログラ ム では、 疑似乱数を使ってマップデータから砂漠のパターンをサーチして、

敵を起動する タイミング としてあります。では、一気に最後のプロジェク

ト ファイ ルであ る

LIST4-10.PRJ

ま で進んでください。

LIST4-9.C ♦define PTY

32

#define SCLDOT 2 ♦define VEND

400*80

♦define SABAKU 6 ♦define STAPTN 7 ♦define ENDPTN 18

extern char far *c_blue; extern char far *c_red;

/* /* /* /* /* /* /* /* /* /*

パターン を 構成する ライン 数

スクロールドッ ト 数

G.V.RAMのEND アドレス */ 砂漠パターン番号 */ 敵バターン アニメーション スタート 番号 */ 敵パターン アニメーションエンド番号

B面への ボイ ンタ */ R面への ボイ ン タ*/ G面への ボイ ン タ*/ 1面への ポイ ン タ*/

extern char far *c_green; extern char far *c itsty; extern int enemy_tablel[20]; /* 敵X座標決定用 */ extern int enemy sign; /* 敵出カサイ ン */ extern int random; /* 乱数格納用 */ extern int px;

/* パターン 格納用構造体 */ extern struct { long dtb[PTY]; long dtr[PTY]; long dtg[PTY]; long dti[PTY]; } pattern[50];

*/

*/

/* パターン X座標 */

*/

166 第 4 章 TV ゲームの世界 /* 敵の状態管理用構造体の定義 */ struct { int inf; int x; int y; int dy; int ptn; } enemy[4]; void frandom(void);

/* 敵の表示用関数の定義 */ void enemy_disp( int enemy set) { int i, j, k, ユ, m, n; if(enemy_set == 1) { for(i = 0; i < 4; ++i) { /* 敵の状態別処理 */ switch(enemy[i].inf) { case 0: /* 敵表示開始処理 */ if(enemy sign != 0) { frandom(); for(j » 0, k = random; j < 20; ++j) { ++k;

-

0; if(k >- 20) k enemy tablel[k]; j SABAKU) { if(j enemy[i].inf •» 1; k-k + k + k + k; enemy[i].x = k; enemy[i].y = 16; enemy[i].dy = enemy sign+k; enemy[i].ptn = STAPTN * 4; =0; enemy sign h



break;

} } } break;

case 1: /* 敵Y座標更新 */ enemy[i].y += SCLDOT; if(enemy[i].y > 400-10*PTY) enemy[i].inf = 2;

敵の出現 167

break;

case 2: /* 敵パターン 更新処理 */ enemy[i].ptn; n n »- 2; if(n >- ENDPTN) enemy[i].inf = 0; for(j = 0, m = enemy[i].dy, k = PTY-1; k) { j < PTY; ++j, »



*(long far *)(c_blue + m)

-

pattern[n].dtb[k];

*(long far *)(c_red + m) = pattern[n].dtr[k]; *(long far *)(c green + m) pattern[n].dtg[k]; *(long far *)(c_itsty + m) pattern[n].dti[k]; m -= 80; if(m < 0) m +- VEND; 工

I ++ enemy[i].ptn; break; default : break;

} }

} else {

/* 敵情報テーブル初期化 */ 0; i < 4; ++i) enemy[i].inf for(i 0; enemy sign

-

LIST4-10.C ♦include #include

♦include ♦include ♦define PTY

♦define NPAT ♦define MAKE_ESC

♦define SCLDOT

32 80*PTY

0 2

-

0;

168 第 4 章 TV ゲームの世界 #define PTYO

42*80*8

#define VSTADR

16

♦define MAKE_RETURN Oxlc

extern int keydat, px; extern int pattern_in(unsigned int, int); extern int pattern_out(unsigned int, int); extern int gload(void); extern void initkey(void); extern void termkey(void); extern int car_disp(int, int); extern void scroll(int, int, int); extern int map_data_load(void); extern void enemy_disp(int); extern void frandom(void);

extern char gdchz;

int

mwindow = 36;

void main() { k, 1, m, n, py, pyO, pye, r; int i, unsigned int kyori;

if(inportb(0x31)

&

0x80) gdchz

else

gdchz

= 0; = 0x40;

grphinit(); pointer_set();

clrscr(); printf("¥n八. ターン データ ロ••ド "); if(gloadO ==0) { for(k = m = n = 0, 1 = mwindow; k < 4; ++k, 1 += NPAT) { for(i =0, j = 1; i < 11; ++i, j+=4) { n = pattern_in(m++, j); if(n != 0) break; }

if(n != 0) break; } map_data__load(); clrscr(); initkey(); gotoxy(1, 1);

敵の出現 169 printf ("RALLY

9 8 " );

keydat = 1;

pyO

= 80 *

VSTADR;

while (keydat != MAKE_ESC) { enemy_disp (0) ; scroll (80 * VSTADR, pyO, 0) ;

i

=

PTYO;

py

«

PTYO;

px

=38;

pyO

= 80 *

pye

-

-

keydat

=

kyori r

=

VSTADR;

400 * 80

-

80 * SCLDOT;

0x70; 0x0;

0;

gotoxy (68, 1) ; printf ("距離");

while (keydat != MAKE_ESC && r == 0) gotoxy (73, 1) ;

printf ("%u

m

{

", kyori);

++kyori;

r = car_disp (py, i) ; while (keydat == 0x70)

frandom() ;

enemy disp (1) ;

scroll (pyO, pye, 1);

i

-

py



py;

80 * SCLDOT; if (py < 0) py = 80 * 400 pyO -■ 80 * SCLDOT; if (pyO < 0) pyO =80 * 400 pye 80 * SCLDOT;

if (pye < 0) pye = 80 * 400 } }

termkey () ; }

else { printf ("¥n press any key"); getch() ;

closegraph () ; }

-

80 * SCLDOT;

-

80 * SCLDOT;

-

80 * SCLDOT;

170 第 4 章 TV ゲームの世界

LIST4-10.PRJ list2-0 list2-l list3-0 list4-0 list4-2 list4-4 list4-6 list4-7 list4-9 list4-10



実行して みる と、 たった一種類の敵ですが今までに ない 雰囲気 と なった はずです。ここでは一種類 の敵 だけ としましたカヌ、 さらに多く の敵を出現 させ、攻撃パターン に工夫を凝らしたり すれば、このゲーム もより面白い ものにな る と 思います。

なお、機種によ って は実行スピード が遅く感じられる場合があ る かと思 い ま すカ*、そ の時に は、MS-DOS のコマンド ライ ンから直接起動して みて

く ださい。 さて、 サンプルプログラム とい う ことで、背景描写や敵のパターンも 含

め て33種類のパターン だけでゲーム を作って みました。市販 のゲーム と比

市販のゲー べる と今一歩の感 は否めない よう です か又 それ もそ の はずです。 ム では、数百 ものパターン を用意する のが普通です。しかも、プログラム、 パターンデザイ ン、グラフィックデザイ ン、シナリオ、音楽 とい うよう に、 それぞれの分野 のプロが専門分野 を担当するという のが一般的ですから、

無理 も ない ことなのです。 と はいい ながら、アイ デアしだいでは世界中に受け入れられる ゲーム が

生まれる 可能性があ り ますから、後は読者の創造性にま かせる こ ととして

本書でのゲーム 作り の幕としましょう。

まとめ 171

まとめ

c 言語によ る ゲーム作り はいかがでしたか、 このよ うにグラフィックス 処理 も 遊び感覚でできるゲーム から入る と楽しく 進められたのではないで しょうか。プログラ ミ ング作業 も 視覚で確かめながら進めていく こ とがで き、しかも、ゲーム は様々なテクニックが要求される ため に、プログラム テクニック の修練 の場 として は最適 とい える のではないでしょうか。

グラフィックス 処理 に は 様々な分野 があ り ます カヌ、 基本 は G.V.RAM

へ の ポインタを どのように 扱う かに尽きる と 思います。CAD、CAM に代 表される CG の世界 も、ファミコンに代表される TV ゲーム の世界 も、す べて G.V.RAM を どの ように 扱う のか とい う 問題へ と行き着いてしまう

のです。

PC-9801シリーズ は、 CAD、CAM や TV ゲーム 用の専用機ではありま せんが、グラフィックス 処理専用 の ハードウェア である GDC (Graphic

Display Controller) や GRCG (Graphic Charger) 、EGC (Enhanced

Graphic Charger) な どが登載されている ため に、かなり の処理が可能に な り ます。これらのハードウェア のコントロール も、本書程度 の G.V.RAM

の処理を把握してい ないと、 やはり困難である とい えます。

ところで、この GDC に関して は、それ だけで一冊の本ができる ぐらい の

内容があ り ますので、本書では GDC の持っている 機能の中でも スクロー ル機能だけに 絞り、他の機能を解説する こと はし ませんでしたが、その他

様々 な機能を応用すれば、 た とえば直線描画機能を光線銃 の軌跡に利用す るな ど、 さらにおもしろいものができる と 思います。

GRCG や EGC に関して は、登載機種が限られてい る ため に、サンプルプ ログ ラム での使用を避けました。しかし、特に EGC に は、シフト 機能を有

した4画面同時転送機能や、ビット 演算機能、さらに EGC と GDC と を組み

合わせて使うな ど、 ゲーム 制作に はうってつけ の機能が豊富にあります。 登載されている 機種であれば、 これらの機能を使わない の は、 まさに「宝

172 第 4 章 TV ゲーム の世界 の持ち腐れ」といったところではないでしょう か。 たとえば、パターン の

多重ス クロー 左右のドット 単位ス クロール処理、 完全な重ね合わせ処理や、 ぜひ、 ルな ど アイ デアしだいで応用範囲 はかなり広い も のがあ り ま すから、

EGC の活用 も 研究して みて下さい。 また、TURBO

C では TURBO アセンブラ と組み合わせる ことによっ

c

て、 のプログラム中にイ ンライ ン アセンブラ としてアセンブリ 言語のプ ログラム を書く こと ができます。 もし、TURBO アセンブラ がなくて も、

MASM で開発したマシン語とのリ ンクが簡単にできる ため に、EGC など の機能が 登載 されていない 機種 を 使用してい る 方や、汎用性 の観点 から

PC-9801シリ ーズ固有の GDC、 GRCG、EGC という ハードウェア は使い た

c

く ない ならば、 言語 とマシン語のリ ンク とい う 方法を検討して みる の も よいでしょう。 もちろんこれらの登載機種であって も、 たとえばパターン

表示関数などのような、特に スピード を要求される一部の処理をマシン語 に置き換える というの は、度々 用いられている手法です。

本書 を ステップに GDC や EGC を駆使した本格的な グラフ ィ ック ス処 理にチャレンジして みて ください。 もしも、マシン語やゲーム に興味があ る 方であれば、『はじめて のマシン語』(啓学出版)、『マシン語ゲームプロ

グラミン グ」(アスキー)「マシン語ゲーム グラフ ィックス」(小学館)など

に も 取り組んでみて ください。 また、最後に なりましたが、本書および続

編に対する 御意見、御希望などありましたら聞かせていただければ幸いで す。

173

索引 こ 構造体 127

あ アスキー コード 7

コピー 95

アニメーション処理 126

コンパイ ラ 2

い 色の成分のチェック 41

-の実行過程

色の混ぜ合わせ 29 インタープリタ 1

-の実行過程

し シフト 演算子 39,48

2

インラインアセンブラ

171

え エンハンスト グラフィック チャ ジャー 16 お

オープン

107

オフセット

——形状データ

セグメント

56

148

17

10

——アドレス

10,21

そ ソースプログラム 1

型変換 130

て ディップスイッチ 149 ディレクト リ内のファイル表示

76 76

115

ーー割り込み 76,113



147

せ セーブ 107,113

48

重ね合わせ処理 155

キー情報

初期化作業 18

——コマンド のパラメータ

拡大表示 64

キーボード

初期化 19

スモールモデル

か カーソル 46,69,84



条件式の評価 42

——コマンド

10,21

オブジェクト ファイル 2

——の表示

実行プログラム 2

す スクロール 145

10

——アドレス

2

コンパイ ル 2

キャスト

130

クローズ

107

デバイスドライバ 53

と 動作クロック 148

グラフ ィックス システム

16,18

グラフ ィックスドライバ

16,19

グラフィックチャージャー 16 グラフィックビデオ ラム

11

ドット 座標

35

ドット 表示

56

に ニブル 6

は バイ ト 6 パターンエディタ 61

174

-の編集機能

メモリ のビット イメージ

98

145

ハードウェア スクロール

汎用 BOX ルーチン 61

も モニタ 9

ら ライブラリ 16

ひ ビット 6

——アドレス ——イメージ -操作 103

36

ろ ロード 107,113

11,126

わ ワイルド カード 113 ワード

ふ ファイ ル管理 107 ファイ ルの捜索

割り込み処理 52,53,69 115

割り込み テーブル 70

ファイル ポインタ 107

割り込みベクタ 69

ファイル名 113

割り込みベクタテープル 52

ファイ ル名の入力 115

割り込みマスクレジスタ 70

フアン クション キー 90

割り込みを許可 59

フアン クション機能 90

割り込み禁止 59

24

フィールド

11,21

プロジェクト ファイル



6

割り込み関数 74

115

ファイル の入出力管理

プレーン

33

メモリ モデル 10,17,74

ベクタテーブル

25,26

数字 10進数 と 4桁の2進数の対応 4 1桁の2進数の足し算 4

74

2進数 4

変数の可視性 22

2進数の単位 6

ほ ポインタ 21

2次元配列 133

ま マウス 53

600X200ドット モード 13

——

ドライ ノヾ 53

600X400ドット モード 12

-ドライ バの組み込み手順 53

——割り込み マシン語コード

56 1

英字

B BASIC

9

BGI 16

マップエディタ 133

BGI.ARC 17

マップデータ 133,165

binary number 4

め メモリ アドレス 36 メモリ 管理 10

binary-digit 6

bit 6

175 Blue(青) 21

GRCG 16,171

Borland Graphics Interface 16

Green(緑) 21

BOX ルーチン 62

G 面 21

byte 6

H Hexadecimal 9

B 面 21

I if 文 42

C cast 130

initgraph ( )

Central Processing Unit 10

CPU 10

D

interrrupt 修飾子 70,74

detectgraph ( )

E EGC

I 面 21

16,18

M MK_FP( ) 22

16.171

Enhanced Graphic Charger 171 exit ( 1 ) 19

f close ( ) 107

PC98GRCG.BGI 16 putimage ( )

findfirst ( ) 115

126

R Red(赤) 21

findnext ( ) 115

R 面 21

First In First Out 146 107

T TURBO アセンブラ 171

fputc ( ) 107

G G.V.RAM

P PC98.BGI 16 PC98EGC.BGI 16

107

FIFO バッファ 146

fopen ( )

N NEC 版マウスドライバ 53 nibble 6

F far タイプの ポインタ 21 fgetc ( )

18,19

Intensity(輝度) 21

11,20,21,22

U UNPACK.EXE 17 unsigned 40,48

,

GDC 145,171

GDC のステータス フラグ 147

V V-SYNC 割り込み 69 void 型 74

getimage( ) 126 Graphic Charger 171

W word 6

Graphic Display Controler 145,171

GRAPHICS. H 17 GRAPHICS. LIB 17 graphresult ( )

19

X XOR(排他的論理和) 46 XOR の真理値 46

X 軸 35

Y Y軸

35

[プログラムソースのディスクサービスのお知らせ] 本書 に 掲載したプログラム のソースコード と パターン データ の入ったディスクを販売いたします。ご希望の方 は巻末 に添 付してある 振り替え用紙 に住所、氏名、電話番号、希望のディ スクメディア をご記入の上、お申し込み下さい。

価格:3000円 (送料込み)

• 本 ディ スク に は TURBO C および BG1フ ア イ ル、MS -DOS システム は含まれていません。 •本ディスク中のプログラムをそのまま、あるいは改変して 使用した結果 として生じた損害について、著作者および出版 者 はいっさいの責任を負いません。 • プログラム から Copyright(c) Manabu Aoyama の表記を 削除しないで下さい。

[著者紹介] 青山 学 1958年 東京生まれ。 青山学院大学理工学部卒業。コンピュータエンジニア を 経て、 現在は ゲーム デザイナー。 大型 コンピュー タからパソコン まで、 言語、 機種にこだわら ない の を 信条 と してい る。著書 と して、CPC-9801シリーズは 『8086マシ ン 語秘伝の書』(以上啓学出版)、 じめてのマシン 語」 「PC-9801シ リーズマシン 語ゲームプロ グラ ミ ン ク*」 (アス キー)、『マシン 語ゲーム グラフィックス」(小学館)などがあ る。

趣味は、 スキー、 テニス など。

PC-9801+ TURBO C グラフィックス に強く なる本 ゲーム 編

©

青山学 1991

1991年9月30日 第 1 刷発行 あお

著 者 青

やま



まなぶ



発行所 啓学 出版 株式 会社 代表者 三井数美

郵便番号 101 東京都 千代田 区 神田 神保 町 1-46 電 話 東京 03(3233) 3795[販売部] 東京 03(3233)3731 [編集部] 振 替 東京 3-109286 印刷/昭和工業写真印刷所

ISBN4-7665-0900-5

Printed in Japan

製本/徳 住 製本 所 本書の定価はカバーに表示してあります。

SHI