ゲーム作りで学ぶ!
実践的 C 言語プログラミング
Nishioはじめにプログラミングには数学やアルゴリズムなどの知識は必要ない．このような話をよく耳にします．たしかにこれらの知識が無くても，プログラム（ゲーム，業務アプリケーション等）を作ることもできます．別に数学なんか一切できなくても（四則演算ができないとダメですが），アルゴリズムのリンクリストやらハッシュ等の知識なんてなくてもプログ
ラムは組めるのです（まともに動くかどうかは別として）．
しかし，質の高いプログラムをつくるためには，数学的な論理的思考力やデータ構造な
どのアルゴリズムの知識はとても大切です．特にゲームプログラミングの分野においては数学的な知識を使わないと，思うようにゲームが作れないかもしれません．
例えば，あなたは今，シューティングゲーム作っているとします．自機が敵に向かって弾を発射したとしましょう．さて，弾が衝突したかどうかをどうやって判定するのでしょうか．この時利用するのが，円と円との衝突判定です．
具体的には，自弾を 円 C1 (中心点 (x1 , y1 ), 半径 r1 ) とし，敵機を 円 C2 (中心点 (x2 , y2 ), 半
径 r2 ) とすると，(x2 − x1 )2 + (y2 − y1 )2 ≤ (r1 + r2 )2 なんていう計算を行う必要があります．
円と円だけでなく，もしかしたら点と円との衝突判定，四角形と円との衝突判定などが必要となるかもしれません．
アルゴリズムの知識が無いと，とんでもなく非効率なプログラムができあがってしまう
かもしれません．特にゲームの分野では，1 秒間に 60 回 (60fps の場合）もの処理を高速で行う必要があります．たとえばシューティングゲームで，自機が 300 発もの弾を発射したとします．この弾を登録する時に，データ構造の知識（リンクリスト等）が必要となってくるかもしれません．
本書では，単にゲームの作り方を説明していくだけでなく，アルゴリズムや数学について，またこれらの知識をどういったときに使ったらよいか等を解説していきたいと思います．

本書の対象読者
本書は，C 言語の基礎を一通りマスターしたが，この先どのようなプログラムを作っていいかわからない方，ゲームの作り方を知りたい，アルゴリズムの知識を身につけたい等の方を対象としています．
本書を読むにあたっては基本的な C 言語の知識が必要です．基本的なとは，例えば if 構
文の使い方が分かる，malloc 等を知っているくらいの知識で十分です．関数ポインタなどの難しい考え方は本書で丁寧に解説していくつもりです．

もし，C 言語の基礎から勉強したいのならば，著者の書いた C 言語プログラミング入門(http://karetta.jp/book-cover/c-for-beginners) を是非読んでみてください．

DX ライブラリについて
C/C++言語は，Java 言語等と違い，標準で GUI を作成するためのライブラリは組み込
まれてはいません．よって，企業や団体，個人が作成したライブラリを利用する事になります．C/C++言語で GUI を作成する為のライブラリ（GUI ツールキット）は数多く存在しています．Windows の場合，WindowsAPI や MFC 等が有名です．Linux の場合，Gtk や Qt等が使われることが多いです．しかし，これらのライブラリはゲームを作成するには向いていません．ゲーム作成で最もよく使われるライブラリは OpenGL や DirectX 等でしょう．
OpenGL は Windows だけでなく，Linux や Unix，ゲーム機の PS2 等にも使われています．しかし，OpenGL はグラフィック専用のライブラリなので，グラフィック以外の機能（例えばサウンド再生，通信機能，入力関係等）は提供していません．
対して，DirectX は Windows（又はゲーム機の XBox 等）でしか利用できませんが，グラフィック処理だけでなく，サウンド再生，通信機能等，ゲーム作りには欠かせない機能が組み込まれています．Windows でゲームを作る際，一番多く採用されているライブラリでしょう．
しかし，この DirectX，機能は豊富なのですが，使い方がとても難しいです．覚えることが多く，ちょっとした処理をするにも一苦労です．著者もいままで DirectX を使ってゲームを作ろうと（何度も）企てていましたが，なにせ初期化だけでも 100 行近くのコードを書かねばならず，挫折してしまったものです．だんだんゲーム作りがいやにもなってきていました．
そんな時偶然見つけたのが山田 巧氏が作成した DX ライブラリでした．DirectX のラッパークラスである DX ライブラリですが，面倒な処理はすべて DX ライブラリが受け持ってくれます．例えば，初期化処理は DxLib Init 関数を呼び出すだけで完了です．画像を表示する，音楽を再生する等の処理も 1 つの関数を呼び出すだけでいいのです．これには驚きました．いままでいくつかの DirectX のラッパーライブラリを使ってきましたが，DX ラ
イブラリが一番直感的で，使いやすいと感じています．本書では，山田氏の作成した DX
ライブラリを利用して，ゲームプログラミングの解説を行って行きたいと思います．

謝辞
著者は絵を書くことができません．音楽等についてのセンスもありません．しかし，画像等の素材が無くてはゲームとして成り立ちません（一部例外もありますが）．
ネット上にはゲーム素材をなんと無料で提供してくださる方がたくさんいます．著者のようにゲームは作りたいが，素材がないものにとっては，これは大変ありがたいことです．
本書でゲームを作成するにあたっては，以下の方々の素材を利用させていただきました．
このようなすばらしい素材を提供してくださった方々に深く感謝申し上げます．

ノベルゲーム作成に使用した素材の作者様
背景画像はぐったりにゃんこ様（http://guttari8.web.infoseek.co.jp/) の素材を利用させていただきました．
メッセージボックスはひだちー素材館様（http://www.vita-chi.net/sozai1.htm) の素材を利用させていただきました．
人物の立ち絵は蓮太郎様（http://sky.geocities.jp/dsrmg137/index.html）の素材を利用させていただきました．

シューティングゲーム作成に使用した素材の作者様
敵グラフィックは，野プリン様 ( http://wild-pd.hp.infoseek.co.jp/ ) のものを利用させていただきました．
画面右側背景はシュガーポット様（ http://sugarpot2.fc2web.com/sozai/in_sozai.htm )のものを利用させていただきました．
背景画像は伊嘉智情大様 ( http://www.ikachi.org/graphic/index.html ) のものを利用させていただきました．
戦闘機画像は夢蒼/musou 様 ( http://game.yu-nagi.com/ ) のものを利用させていただきました．
爆発画像は MIA 様の発色弾 ( http://taillove.jp/mia/# ) を利用させていただきま
した．

7

目次
第I部

DX ライブラリの基礎

1

第 1 章 開発環境を整える
1.1 DX ライブラリのダウンロード . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 VC++プロジェクトの環境設定 . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 サンプルプログラムの実行 . . . . . . . . . . . . . . . . . . . . . . . . . . .

3
3
4
10

第2章
2.1
2.2
2.3
2.4
2.5

文字列描画の基礎
画面に文字列を表示する . . . . . . . . . . . . . . . . . . . . . . . . . . . .
書式付き文字列の描画 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
フォントサイズの変更 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
フォントの変更 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
コンソールへの文字列出力 . . . . . . . . . . . . . . . . . . . . . . . . . . .

11
11
14
15
15
16

第3章
3.1
3.2
3.3
3.4
3.5

画像データ制御の基礎
画像ファイルの表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
画像の透過処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
メモリ上にある画像を表示 . . . . . . . . . . . . . . . . . . . . . . . . . . .
Windows からのメッセージ処理 . . . . . . . . . . . . . . . . . . . . . . . .
ダブルバッファリング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

19
19
20
22
23
25

第4章
4.1
4.2
4.3
4.4

入力関係処理の基礎
特定のキーの入力状態の取得 . . . . . . . . . . . . . . . . . . . . . . . . . .
キーの入力状態の取得 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
マウス座標の取得 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
マウスボタンの状態の取得 . . . . . . . . . . . . . . . . . . . . . . . . . . .

29
29
31
32
33

第 5 章 サウンド取り扱いの基礎
5.1 サウンドの再生 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 メモリ上にあるサウンドの再生 . . . . . . . . . . . . . . . . . . . . . . . .
5.3 サウンドデータの音量調節 . . . . . . . . . . . . . . . . . . . . . . . . . . .

37
37
38
40

第 II 部

ノベルゲームの作成

43

第6章
6.1
6.2
6.3
6.4
6.5
6.6
6.7
6.8
6.9

サウンドノベル風メッセージ表示
指定範囲の英字文字列を表示 . . . . . . . . . . . . . . . . . . . . . . . . . .
指定範囲の日本語文字列を表示 . . . . . . . . . . . . . . . . . . . . . . . .
日本語文字の判定方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
指定範囲の文字列を表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
指定範囲の文字列を表示 GUI 版 . . . . . . . . . . . . . . . . . . . . . . . .
メッセージを 1 文字ずつ表示 . . . . . . . . . . . . . . . . . . . . . . . . . .
改行処理を加えたメッセージ描画 . . . . . . . . . . . . . . . . . . . . . . .
メッセージボックスの表示 . . . . . . . . . . . . . . . . . . . . . . . . . . .
サウンドノベル風メッセージ表示プログラム . . . . . . . . . . . . . . . . .

45
46
47
48
50
51
55
56
59
64

第7章
7.1
7.2
7.3
7.4
7.5
7.6
7.7

グラフィック管理
メモリ上に画像を読み込む . . . . . . . . . . . . . . . . . . . . . . . . . . .
画面に表示する画像の管理 . . . . . . . . . . . . . . . . . . . . . . . . . . .
画面に表示された画像の削除 . . . . . . . . . . . . . . . . . . . . . . . . . .
画像のフェードイン・フェードアウト . . . . . . . . . . . . . . . . . . . . .
輝度情報を持った画像の管理 . . . . . . . . . . . . . . . . . . . . . . . . . .
グラフィック管理プログラム . . . . . . . . . . . . . . . . . . . . . . . . . .
グラフィック管理プログラムの改良 . . . . . . . . . . . . . . . . . . . . . .

69
70
74
80
82
87
91
96

第8章
8.1
8.2
8.3
8.4

選択肢の表示選択肢ボックスの表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
マウスカーソルがボックス内に存在するかの判定 . . . . . . . . . . . . . .
選択肢ボックスにメッセージを入れる . . . . . . . . . . . . . . . . . . . . .
選択肢の表示プログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . .

101
101
104
108
112

第9章
9.1
9.2
9.3
9.4
9.5
9.6
9.7
9.8
9.9

ノベルゲーム用スクリプト言語の作成
スクリプトを読み込む . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
空白空行を飛ばして読み込む . . . . . . . . . . . . . . . . . . . . . . . . . .
文字列分割 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
字句・構文解析器の作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ラベルの検索 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
条件分岐構文の作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
指定したラベルへのジャンプ . . . . . . . . . . . . . . . . . . . . . . . . . .
ノベルゲーム用スクリプト言語解析プログラム . . . . . . . . . . . . . . .
ノベルゲーム用スクリプト言語解析プログラムの改良 . . . . . . . . . . . .

117
117
120
122
124
127
131
136
140
146

第I部

DX ライブラリの基礎

3

第 1 章 開発環境を整える
この章では，DX ライブラリを利用するための開発環境を整えていきます．なお，C/C++
コンパイラは Visual C++ 2008 Express Edition を利用します．このコンパイラはインストー
ル済みである事が前提となります．

1.1 DX ライブラリのダウンロード
まずは，DX ライブラリをダウンロードしましょう．以下のページから最新版を入手することができます．

http://homepage2.nifty.com/natupaji/DxLib/dxdload.html

図 1.1: DX ライブラリのダウンロード

1.2 VC++プロジェクトの環境設定
今回は VisualC++ Express Edition 2008 をコンパイラとして使用することとします．よって，DX ライブラリは VisualC++用をダウンロードしてください（図 1.1)．ダウンロードする場所は，仮に C:\Game\としましょう．なお，VisualC++2008 はインストール済みであることが前提です．もし，持っていないのなら，

http://www.microsoft.com/japan/msdn/vstudio/express/
から無償でダウンロード可能なので，インストールしておいてください．
ダウンロードが完了したら，DX ライブラリを展開してください．図 1.2 のように展開されれば成功です．

図 1.2: DX ライブラリの展開

1.2 VC++プロジェクトの環境設定
DX ライブラリを利用したプログラムをコンパイルするための VC++プロジェクトを作成してみましょう．まずは，Visual C++ 2008 を起動してください．
次に，メニューバーのファイル → 新規作成 → プロジェクトをクリックしてください (図
1.3）．

4

第 1 章 開発環境を整える

図 1.3: プロジェクトの作成

空のプロジェクトを選択してください．プロジェクトの種類，全般にあります（図 1.4）．
インストール場所は，C:\Game とします．プロジェクト名は何でもよいですが，ここではDxlibTest としましょう．

図 1.4: 空のプロジェクトの作成

プロジェクト作成完了後，ソリューションエクスプローラ内にある，ソースファイルフォ
ルダを右クリックし，新しい項目をクリックします (図 1.5）．
C++ファイルを選択し，追加をクリックしてください（図 1.6）．ファイル名は何でもよいですが，ここでは test としましょう．
次に，DX ライブラリを利用するための設定を行います．メニューバーのツール → オプ
ションを選択してください（図 1.7）．
オプションのプロジェクトおよびソリューションにある，VC++ディレクトリを選択します．そして，ディレクトリを表示するプロジェクトからインクルードファイルを選択し

5

1.2 VC++プロジェクトの環境設定

図 1.5: 新しい項目の追加

図 1.6: cpp ファイルの追加

図 1.7: オプションの設定

6

第 1 章 開発環境を整えるてください（図 1.8）．

図 1.8: インクルードファイルの設定

図 1.9 の赤丸で囲ったフォルダマークをクリックします．すると，空白のテキストボッ
クスの右側にボタンがでてきます．そのボタンをクリックし，ディレクトリの選択を行います．ディレクトリは，先ほど展開した DXLib フォルダの「プロジェクトに追加すべきファイル VC 用」を選択してください．
同様に，今度はディレクトリを表示するプロジェクトのライブラリファイルを選択し，ディレクトリの選択を行ってください．選択するディレクトリは先ほどと同じ「プロジェクトに追加すべきファイル VC 用」です．図 1.10 のように設定できれば完了です．
次に，メニューバーのプロジェクト → DxLibTest のプロパティをクリックしてください（図 1.11）．
構成プロパティ内の C/C++ → コード生成を選択します．そして，ランタイムライブラ
リをマルチスレッドデバッグ DLL からマルチスレッドデバッグに変更してください（図1.12）．
以上で VC++の環境設定は完了です．

7

1.2 VC++プロジェクトの環境設定

図 1.9: インクルードファイルの設定

図 1.10: ライブラリファイルの設定

8

第 1 章 開発環境を整える

図 1.11: プロジェクトプロパティの設定

図 1.12: マルチスレッドデバッグの設定

9

1.3 サンプルプログラムの実行

1.3

サンプルプログラムの実行

正しく開発環境が整えられたかどうかを確かめましょう．以下のソースコードを test.cpp
内に書き込んでください．
1#include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow ){if( DxLib_Init() == -1 ) {return -1;}
DrawString(50, 50, "Hello DX ライブラリ", GetColor(255,255,255) );WaitKey();
DxLib_End();12

return 0;

13
14

}

プロジェクトをコンパイルし，実行してみてください．画面に Hello DX ライブラリと表示されれば完了です．

10

11

第 2 章 文字列描画の基礎
この章では，文字列描画関係の関数について学んでいきます．

2.1

画面に文字列を表示する

Hello World プログラムを作成してみる事にしましょう．Hello World プログラムとは，画面にただ Hello World と表示するだけのプログラムです．まずは，プログラムをお見せします．
リスト 2.1: ”foundation-01.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

12// 文字列表示
DrawString ( 20, 20, "Hello DX Library !", GetColor (255 , 255 , 255));

13
14
15

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

16
17
18
19
20
21

}

上記のプログラムは，座標 (20, 20) に Hello DX Library!と白文字で表示するプログラムです（図 2.1）．

図 2.1: Hello World プログラム

2.1 画面に文字列を表示する
見慣れない関数がずらりと並んでいます．先頭から順番に説明していきたいと思います．
1 行目では，DxLib.h のインクルードを行っています．これは，DX ライブラリ特有の関数を利用する場合は必ず必要です．
次に，以下のコードをご覧下さい．
1#include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow ){//ウィンドウモードで起動
ChangeWindowMode( TRUE );//DX ライブラリ初期化
if( DxLib_Init() == -1 ) {return -1;}

12//ここにプログラムを書いていく

13
14//DX ライブラリ終了処理
DxLib_End();return 0;

15
16
17
18

}

上記のコードは覚える必要はありません．
「ここにプログラムを書いていく」という部分にプログラムを書いていくということを覚えて置いてください．このテンプレートは毎回利用します．
一応少し解説をしておきます．WinMain 関数ですが，これはプログラムが実行された際に，一番初めに呼び出される関数です．コンソールアプリケーションの場合，一番初めに呼び出される関数は main ですが，Windows アプリケーションの場合，WinMain 関数が呼び出されます．これは，DX ライブラリに限った話ではなく，Windows アプリケーション
は，必ず WinMain 関数から始まります．
次に，ChangeWindowMode 関数ですが，これはフルスクリーンかウィンドウモードかを切り替えるための関数です．DX ライブラリは，標準ではフルスクリーンで起動されます．
しかし，フルスクリーンで起動されるのは何かと嫌なものなので，ChangeWindowMode 関数を利用してウィンドウモードとしています．引数には TRUE 又は FALSE を指定できます．TRUE にするとウィンドウモードで起動されます．
DxLib Init 関数は，DX ライブラリの初期化を行うプログラムです．この関数は，初期化に失敗した際，戻り値として-1 を返します．よって，何らかのトラブルが発生して初期化に失敗し，-1 が帰ってきた場合，プログラムを終了させる必要があります．
DxLib End 関数は，DX ライブラリを終了する際に利用します．この関数は，メモリ領域の開放や，ウィンドウを閉じる等の処理を行ってくれます．

12

第 2 章 文字列描画の基礎
では，話を戻して，DrawString 関数の説明に入ります．DrawString 関数は文字列を画面に描画する際に利用します．関数の定義は次のようになっています．

int DrawString( int x , int y , char *String , int Color );第 1, 第 2 引数は，文字を描画する座標を指定します．座標は画面左上を (0, 0) としています（図 2.2)．

図 2.2: 座標

DrawString 関数の第 3 引数には描画したい文字列を，第 4 引数には文字列の色を指定します．
ここで，第 4 引数に色コードを渡すのですが，その際利用するのが GetColor 関数です．
GetColor 関数の定義は次のようになっています．
int GetColor( int Red , int Green , int Blue );Red, Green, Blue には取得したい色の輝度値を指定します．これらの要素の限界値は 255
です．例えば，赤色コードを取得したい場合，
GetColor(255, 0, 0)とすればよいわけです．今回は白色を取得したいので，

GetColor(255, 255, 255)
としています．
WaitKey 関数ですが，これはキー（又はマウス）の入力待ちを行う際に利用します．

13

2.2 書式付き文字列の描画

2.2

書式付き文字列の描画

DX ライブラリには書式付の文字列を表示できる DrawFormatString 関数が用意されています．これは，printf 関数と同じように使うことができる関数です．この関数を利用して，画面に文字を表示させてみましょう．
リスト 2.2: ”foundation-02.cpp”
1# include "DxLib .h"

2
3
4
5
6
7

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){char message [] = " Hello world";
int hoge = 35;

8// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

9
10
11
12
13
14
15

// 文字列表示
DrawFormatString ( 20, 20, GetColor (255 , 255 , 255) ," message : %s -- value : %d", message , hoge );

16
17
18
19

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

20
21
22
23
24
25

}

このプログラムの実行結果は図 2.3 となります．

図 2.3: 書式付文字列の表示

今回利用した DrawFormatString 関数の定義は以下のようになっています．

int DrawFormatString( int x , int y , int Color ,char *FormatString , ... );第 1, 第 2 引数は文字列を描画する座標を，第 3 引数には色コードを指定します．第 4 引
数以降は，printf 関数と同じです．ただし，一部エスケープシークエンスを利用することができません．それは，\n や\t 等です．改行等の処理は自分で行う必要があります．

14

第 2 章 文字列描画の基礎

2.3

フォントサイズの変更

表示するフォントサイズを変更する事もできます．その際に利用するのが，SetFontSize
関数です．
リスト 2.3: ”foundation-03.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

12// フォントサイズを 25 に す る
SetFontSize ( 25 );

13
14
15

// 文字列表示
DrawString (20, 20, "Hello DX library !", GetColor (255 , 255 , 255) );

16
17
18

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

19
20
21
22
23
24

}

このプログラムの実行結果は図 2.4 となります．

図 2.4: フォントサイズの変更

今回利用した SetFontSize 関数の定義は以下のようになっています．

int SetFontSize( int FontSize );引数にはフォントの大きさを指定します．

2.4

フォントの変更

フォントを変更することもできます．利用するのは，ChangeFont 関数です．今回は，フォントを MS 明朝に変えて文字列を描画したいと思います．

15

2.5 コンソールへの文字列出力
リスト 2.4: ”foundation-04.cpp”
1# include "DxLib .h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

12

// 白色
int white = GetColor (255 , 255, 255);

13
14
15

// 文字列表示
DrawString (20 , 20, " はろーわーるど ", white );

16
17
18

// フォントを M S 明 朝 に 変 え る
ChangeFont (" ＭＳ 明 朝 ");

19
20
21

// 明朝文字で文字列描画
DrawString (20 , 50, " はろーわーるど ", white );

22
23
24

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

25
26
27
28
29
30

}

このプログラムの実行結果は図 2.5 となります．

図 2.5: フォントの変更

今回利用した ChangeFont 関数の定義は以下のようになっています．

int ChangeFont( char *FontName );引数にはフォントの名前を指定します．

2.5

コンソールへの文字列出力

複雑なプログラムを作成していると，デバッグ用メッセージを表示したくなる場合があります．しかし，DX ライブラリを利用している場合，コンソール画面が起動されていな

16

第 2 章 文字列描画の基礎いので，printf 関数を利用することができません．そこで，今回はコンソールを起動する
方法をお見せします．
リスト 2.5: ”foundation-05.cpp”# include "DxLib.h"

1
2

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

3
4
5
6
7
8
9
10
11
12

// デバッグ用にコンソールを呼び出す
AllocConsole ();freopen (" CONOUT$ ", "w", stdout );freopen (" CONIN$ ", "r", stdin );

13
14
15
16
17

// p r i n t f 関 数 を 利 用
printf (" Hello world!");

18
19
20

// キー入力待ち
WaitKey ();// コンソール解放
FreeConsole ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

21
22
23
24
25
26
27

}

28

このプログラムの実行結果は図 2.6 となります．

図 2.6: コンソールの呼び出し

コンソールを呼び出すには，AllocConsole 関数を利用します．これは，DX ライブラリの関数ではなく，Windows 用の関数です．しかし，ただコンソールを呼び出しただけでは，
画面に文字を表示することができません．そこで，標準入力，標準出力を freopen 関数を利用してコンソールへと割り当てています．これらの関数の意味は理解しなくてかまいま
せん．とにかく，
1

AllocConsole();17

2.5 コンソールへの文字列出力
freopen("CONOUT$", "w", stdout);
freopen("CONIN$", "r", stdin);23とすれば，コンソールが呼び出せるということを覚えておいてください．また，コンソー
ルを使い終わったら，FreeConsole 関数を利用して，コンソールを解放してやる必要があ
ります．

18

19

第 3 章 画像データ制御の基礎
この章では，画像描画関係の関数について学んでいきます．

3.1

画像ファイルの表示

画像を画面に表示するプログラムを作成しましょう．今回表示させる画像は図 3.1 です．
まずは，作成したプログラムをお見せします．

図 3.1: カエル (蓮太郎氏作)

リスト 3.1: ”foundation-06.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

12// 画像描画
LoadGraphScreen ( 20, 20, " kaeru1 .png", TRUE );

13
14
15

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

16
17
18
19
20
21

}

上記のプログラムの実行結果は図 3.2 となります．
画像を表示させるために，LoadGraphScreen 関数を利用しました．この関数の定義は以下のようになっています．

int LoadGraphScreen( int x , int y , char *GraphName , int TransFlag );3.2 画像の透過処理

図 3.2: 画像の表示

第 1，第 2 引数には画像を表示する座標を指定します．第 3 引数は，表示したい画像のパスを指定します．ここで，kaeru1.png を読み込みたい場合は，kaeru1.png を Visual C++
プロジェクトがある場所と同じフォルダに置いてください（図 3.3)．

図 3.3: 画像ファイルの保存場所

また，例えばプロジェクトがある下のフォルダ，Pic に保存されている kaeru1.png を読み込みたい場合は， "Pic/kaeru1.png" 又は "./Pic/kaeru1.png" 等と指定します．
LoadGraphScreen 関数が読み込むことができる画像ファイル形式は，BMP, JPEG, PNG,
DDS, ARGB, TGA の 6 種類です. GIF 画像等，読み込めない画像形式に注意してください．
第 4 引数には透過色を入れるか入れないかを指定します．TRUE を指定することで，透過色が有効となります．この第 4 引数に関しては，次節で詳しく説明します．

3.2

画像の透過処理

LoadGraphScreen 関数の第 4 引数 (TransFlag) を TRUE とすることで，画像の透過処理を行うこともできます．透過処理とは，画像の一部分を透明化させることです．まずは，透過処理の例をお見せします．今回は背景画像 back.png とカエルの画像 kaeru1.png を利用しています．
リスト 3.2: ”foundation-07.cpp”

20

第 3 章 画像データ制御の基礎

1# include "DxLib.h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

12// 背景描画
LoadGraphScreen ( 0, 0, "back.png", FALSE );// 画像描画 透 過 処 理 無 しLoadGraphScreen ( 20, 20, " kaeru1 .png", FALSE );// 画像描画 透 過 処 理 有 りLoadGraphScreen ( 100, 20, " kaeru1 .png", TRUE );

13
14
15
16
17
18
19

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

20
21
22
23
24
25

}

このプログラムの実行結果は，図 3.4 となります．

図 3.4: 透過処理の例

図 3.4 の左側のカエルは透過処理を行っておらず，右側のカエルは透過処理をしています．透過処理をするためには，TransFlag を TRUE とすることで，画像ファイルの限りなく黒色に近い部分，又はあらかじめ透過処理されている部分を透明色とします．
透過処理の対象となる色は，デフォルトでは黒となっています．しかし，黒色を透過して欲しくない場合もあります．そういった時には，透過色を設定する SetTransColor 関数
を利用します．

int SetTransColor( int Red , int Green , int Blue );例えば，赤色を透過色としたい場合は，SetTransColor(255, 0, 0); とします．

21

3.3 メモリ上にある画像を表示

3.3

メモリ上にある画像を表示

メモリ上に画像をあらかじめロードしておき，その後画面に表示させてみましょう．通常は，画像を画面に表示させる際，LoadGraphScreen 関数は利用しません．それは，LoadGraphScreen 関数は呼び出された時点で画像ファイルをディスクから読み込んでくるからです．ハードディスクからファイルを読み込むのは，メモリからの読み込みと比べかなり処理に時間がかかってしまうものです．そこで，画像をメモリ上に読み込んでおき，そこから必要なときに画面に表示させるといった方法が取られます．
メモリ上に画像を読み込むには，LoadGraph 関数を利用します．

int LoadGraph( char *FileName );FileName には読み込みたい画像のパスを指定します．この関数は画像の読み込みに成功した際，戻り値にグラフィックハンドルと呼ばれる画像識別番号が返ってきます（画像読
み込み失敗時には-1 が返ってきます）．この識別番号を DrawGraph 関数に渡してやること
で画像を表示することができます．
DrawGraph 関数の定義は次のようになっています．
int DrawGraph( int x, int y, int GrHandle, int TransFlag );使い方は LoadGraphScreen 関数とほぼ同じです．第 3 引数にグラフィックハンドルを渡すことで，画像を表示させることができます．
では，LoadGraph 関数及び DrawGraph 関数を利用したプログラムをお見せします．
リスト 3.3: ”foundation-08.cpp”
1# include "DxLib .h"

2
3
4
5
6
7

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// 画像グラフィックハンドル
int gHandle ;8// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

9
10
11
12
13
14
15

// 画像をメモリに読み込む
gHandle = LoadGraph (" kaeru1 .png");// メモリ上の画像を描画
DrawGraph ( 20, 20, gHandle , TRUE );DrawGraph ( 80, 20, gHandle , TRUE );

16
17
18
19
20
21

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

22
23
24
25
26
27

}

このプログラムの実行結果は図 3.5 となります．

22

第 3 章 画像データ制御の基礎

図 3.5: メモリ上に読み込んだ画像の表示

3.4 Windows からのメッセージ処理
次のようなプログラムを書いてみました．これは，一定時間画像を表示するプログラムです．
リスト 3.4: ”foundation-09.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// 画像グラフィックハンドル
int gHandle ;int i;

9// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

10
11
12
13
14
15
16

// 画像をメモリに読み込む
gHandle = LoadGraph (" kaeru1 .png");

17
18
19

// W i n d o w s か ら の 要 求 を 受 け 付 け な い
for(i = 0; i < 50; i++ ) {// メモリ上の画像を描画
DrawGraph ( 20, 20, gHandle , TRUE );DrawGraph ( 80, 20, gHandle , TRUE );

20
21
22
23
24
25

// 100 m s 待 つSleep( 100 );// 画面上に描かれたものを削除
ClearDrawScreen ();

26
27
28
29

}

30
31

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

32
33
34
35
36
37

}

時間待ちには Sleep 関数を利用しています．この関数には，引数として待ち時間 (ms 単位）を指定します．例えば，0.1 秒 = 100 ミリ秒の時間待ちをしたい場合は，100 を渡します．また，ClearDrawScreen 関数は，画面に描画されたものを削除するための関数です．

23

3.4 Windows からのメッセージ処理
さて，一見何も問題が無い様に見えますが，実行するとやや違和感を感じるかもしれま
せん．たしかに画像は表示されているのですが，ウィンドウの移動や×ボタンを押しての終了等ができません．なぜでしょうか．
実は，これは Windows 側から送られてくるメッセージの処理をしていないからなのです．Windows は，様々なメッセージをプログラムに送っているのです．例えば，画面を移動してくれ，プログラムを終了してくれ等のメッセージです．このようなメッセージを処理しないと，ゲームが不安定になってしまいます．
通常の Windows プログラミングにおいては，この処理は自分で書く必要があるのですが，DX ライブラリでは，そのような面倒なメッセージ処理を肩代わりしてくれる関数が存在しています．それが，ProcessMessage 関数です．上記のプログラムのように，長い間ループ内で処理をしている時には，ProcessMessage 関数を呼びだして，メッセージ処理をする必要があります．
ProcessMessage の定義は次のようになっています．

int ProcessMessage( void );この関数は，エラーが発生した際には，戻り値として-1 を返してきます．よって，エラー発生時には，直ちにプログラムを終了させてやる必要があります．
では，先ほどのプログラムのループ内に ProcessMessage 関数を入れてみましょう．
リスト 3.5: ”foundation-10.cpp”
1# include "DxLib .h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// 画像グラフィックハンドル
int gHandle ;int i;

9
10
11
12
13
14
15

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

16
17
18

// 画像をメモリに読み込む
gHandle = LoadGraph (" kaeru1 .png");

19
20
21
22
23

for(i = 0; i < 50; i++ ) {// メモリ上の画像を描画
DrawGraph ( 20, 20, gHandle , TRUE );DrawGraph ( 80, 20, gHandle , TRUE );24// W i n d o w s か ら の メ ッ セ ー ジ を 処 理
if( ProcessMessage () == -1 ) {break;
}

25
26
27
28
29

// 100 m s 待 つSleep( 100 );// 画面上に描かれたものを削除
ClearDrawScreen ();

30
31
32
33
34

}

24

第 3 章 画像データ制御の基礎

35// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

36
37
38
39
40
41

}

今度はどうでしょうか．ウィンドウを動かしたり，×ボタンを押してプログラムの終了
ができるはずです．

3.5

ダブルバッファリング

今回は画像を動かしてみたいと思います．次のようなプログラムを書いてみました．
リスト 3.6: ”foundation-11.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// 画像グラフィックハンドル
int gHandle ;int i;

9// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

10
11
12
13
14
15
16

// 画像をメモリに読み込む
gHandle = LoadGraph (" kaeru1 .png");

17
18
19

for(i = 0; i < 500; i++ ) {// メモリ上の画像を描画
DrawGraph ( i, 20, gHandle , TRUE );

20
21
22
23

// W i n d o w s か ら の メ ッ セ ー ジ を 処 理
if( ProcessMessage () == -1 ) {break;
}

24
25
26
27
28

//10 m s 待 つSleep( 10 );// 画面上に描かれたものを削除
ClearDrawScreen ();

29
30
31
32

}

33
34

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

35
36
37
38
39
40

}

25

3.5 ダブルバッファリング
画面上を画像が動いていますが，よくよく見ると画像にちらつきが生じています．これは，画像を表示途中の画面が表示されているためです．これを解消するために，ダブルバッ
ファリングと呼ばれるちらつき回避の技術が必要となります．
ダブルバッファリングは画面を 2 つ利用します．一つは実際に表示する表画面（フロントバッファ）と，計算途中の画像を書いておく裏画面（バックバッファ）です．裏画面で画像描画等の処理を行い，すべての処理が完了したら，裏画面と表画面を入れ替える（スワッピング）処理を行います．これにより，画面の変更が一度に行われるため，見た目にはスムーズに表示されるわけです．
さて，DX ライブラリでダブルバッファリングを行うのは簡単です．まずは，描画先の画面を変更します．通常は表画面に直接描画されますが，ダブルバッファリングを利用するために，描画先を裏画面に変える必要があります．その際利用するのが SetDrawScreen
関数です．

int SetDrawScreen( int DrawScreen );DrawScreen には描画先の画面を指定します．描画先を表画面とするには，DX SCREEN FRONT
を，裏画面とするには DX SCREEN BACK を指定します．
裏画面の内容を表画面に反映させるには，ScreenFlip 関数を利用します．
int ScreenFlip( void );注意点として，ScreenFlip 関数を呼び出した後は，ClearDrawScreen 関数を呼び出して
裏画面の情報を削除してください．
では，先ほどのプログラムにダブルバッファリング処理を加えてみましょう．
リスト 3.7: ”foundation-12.cpp”
1# include "DxLib .h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// 画像グラフィックハンドル
int gHandle ;int i;

9
10
11
12
13
14
15

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

16
17
18

// 裏画面への描画をデフォルトとする
SetDrawScreen ( DX_SCREEN_BACK );

19
20
21

// 画像をメモリに読み込む
gHandle = LoadGraph (" kaeru1 .png");

22
23
24
25

for(i = 0; i < 500; i++ ) {// メモリ上の画像を描画
DrawGraph ( i, 20, gHandle , TRUE );26

26

第 3 章 画像データ制御の基礎// W i n d o w s か ら の メ ッ セ ー ジ を 処 理
if( ProcessMessage () == -1 ) {break;}// 裏画面反映
ScreenFlip ();

27
28
29
30
31
32
33

//10 m s 待 つSleep( 10 );// 画面上に描かれたものを削除
ClearDrawScreen ();

34
35
36
37

}

38
39

// キー入力待ち
WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

40
41
42
43
44
45

}

実行結果は図 3.6 となります．どうでしょうか．ちらつきが解消されているはずです．

図 3.6: ダブルバッファリング

27

29

第 4 章 入力関係処理の基礎
この章では，キーボードやマウスからの入力処理等の基礎について解説していきます．

4.1

特定のキーの入力状態の取得

特定のキーが押されているかを判定するには，CheckHitKey 関数を利用します．

int CheckHitKey( int KeyCode );KeyCode には入力状態を取得したいキーコードを渡します（表 4.1）．例えば，A キーが押されているかを判定したい場合は，表 4.1 より KeyCode に KEY INPUT A を渡してやればよいわけです．また，この関数はキーが押されている場合には 1 を，押されていない
場合は 0 を戻り値として返してきます．
では，A キーが押されるまで処理を続けるプログラムを作成してみましょう．
リスト 4.1: ”foundation-13.cpp”
1# include "DxLib.h"

2
3
4
5
6

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white;7// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

8
9
10
11
12
13
14

// 白色取得
white = GetColor ( 255, 255, 255 );

15
16
17

// A キ ー が 押 さ れ る ま で プ ロ グ ラ ム を 続 行
while( CheckHitKey ( KEY_INPUT_A ) == 0 ) {if( ProcessMessage () == -1 ) {break;}DrawString ( 20, 20, " A キ ー で プ ロ グ ラ ム 終 了 ", white );
}

18
19
20
21
22
23
24
25

// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

26
27
28
29

}

このプログラムの実行結果は，図 4.1 となります．

4.1 特定のキーの入力状態の取得

図 4.1: キー入力を受け付けるプログラム

キーコード

キー

キーコード

キー

KEY INPUT BACKKEY INPUT TAB
KEY INPUT RETURN
KEY INPUT LSHIFTKEY INPUT RSHIFTKEY INPUT LCONTROLKEY INPUT RCONTROLKEY INPUT ESCAPEKEY INPUT SPACE
KEY INPUT PGUP
KEY INPUT PGDN
KEY INPUT END
KEY INPUT HOMEKEY INPUT LEFT
KEY INPUT UP
KEY INPUT RIGHTKEY INPUT DOWN
KEY INPUT INSERTKEY INPUT DELETE
KEY INPUT MINUS
KEY INPUT CAPSLOCKKEY INPUT PAUSEKEY INPUT YEN
KEY INPUT PREVTRACK
KEY INPUT PERIODKEY INPUT SLASHKEY INPUT LALTBackSpace
Tab
Enter
左シフト
右シフト
左コントロール
右コントロール
EscapeSpace
PageUp
PageDown
End
Home
左
上
右
下
InsertDeleteCaspLock
PauseBreak
￥
＾
．
／
左 ALT

KEY INPUT RALTKEY INPUT SCROLL
KEY INPUT SEMICOLON
KEY INPUT COLON
KEY INPUT LBRACKETKEY INPUT RBRACKETKEY INPUT AT
KEY INPUT BACKSLASHKEY INPUT COMMA
KEY INPUT MULTIPLY
KEY INPUT ADD
KEY INPUT SUBTRACT
KEY INPUT DECIMAL
KEY INPUT DIVIDEKEY INPUT NUMPADENTER
KEY INPUT NUMPAD0・・・
KEY INPUT NUMPAD9KEY INPUT F1・・・
KEY INPUT F12
KEY INPUT A・・・
KEY INPUT Z
KEY INPUT 0・・・
KEY INPUT 9

右 ALT

表 4.1: キーとキーコードの関係

30

ScrollLock
；
：
［
］
＠
＼
，
テンキー＊
テンキー＋
テンキー−
テンキー．
テンキー／
テンキーの Enter
テンキー 0
テンキー [

F1
F12
A
Z
0
9

第 4 章 入力関係処理の基礎

4.2

キーの入力状態の取得

前節で利用した CheckHitKey 関数は，一つのキーの状態を取得する為のものでした．しかし，複数のキーが押されているかを調べるために，何回も CheckHitKey 関数を呼び出すのは，処理の効率がよくありません．そこで，今回はすべてのキーの入力状態を一度に取得することができる CheckHitKeyStateAll 関数を利用してみます．
CheckHitKeyStateAll 関数は以下のように定義されています．

int GetHitKeyStateAll( char *KeyStateBuf );KeyStateBuf には char 型配列（要素数 256 個）を渡します．要素数は多くても少なくてもいけません．これは DX ライブラリの仕様によるものです．
KeyStateBuf には，全てのキーの入力状態が格納されます．特定のキーが押されているかを調べるには，配列の要素番号をキーコードとして，値が 1 か 0 かをチェックすればよいわけです．
例えば A キーが押されているかを調べるには，以下のコードを利用します．
1
2char keyStatus[256];
GetHitKeyStateAll( keyStatus );

3
4
5
6

if( keyStatus[ KEY_INPUT_A ] == 1 ) {//A キーが押されている時の動作}
では，今回は GetHitKeyStateAll 関数を利用して，A キーと B キーが同時に押された際に終了処理を行うプログラムを作成してみましょう．
リスト 4.2: ”foundation-14.cpp”

1# include "DxLib.h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white;// キー状態
char keyStatus [ 256 ];

9
10
11
12
13
14
15

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

16
17
18

// 白色取得
white = GetColor ( 255, 255, 255 );

19
20
21
22
23
24

//A + B キ ー が 押 さ れ る ま で プ ロ グ ラ ム を 続 行
while( 1 ) {if( ProcessMessage () == -1 ) {break;}

31

4.3 マウス座標の取得// キーの状態を取得
GetHitKeyStateAll ( keyStatus );

25
26
27

if( keyStatus [ KEY_INPUT_A ] && keyStatus [ KEY_INPUT_B ] ) {break;}DrawString ( 20, 20, "A + B キ ー で プ ロ グ ラ ム 終 了 ", white );

28
29
30
31

}

32
33

// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

34
35
36
37

}

このプログラムの実行結果は図 4.2 となります．

図 4.2: 複数のキー入力を受け付けるプログラム

4.3

マウス座標の取得

マウスカーソルの位置情報を取得するには，GetMousePoint 関数を利用します．この関数の定義は以下のようになっています．

int GetMousePoint( int *XBuf, int *YBuf );XBuf, YBuf にはマウスカーソルの x 座標，y 座標が代入されます．
マウスカーソルの座標を取得する際，マウスカーソルが表示されていなければ意味がありません．そこで，SetMouseDispFlag 関数を利用して，マウス表示の有無を設定する必要があります．
int SetMouseDispFlag( int DispFlag );DispFlag には TRUE 又は FALSE を渡します．TRUE でマウスカーソルが表示されます．
では，これらの関数を利用して，マウスの座標を取得するプログラムを作成してみましょう．
リスト 4.3: ”foundation-15.cpp”
1# include "DxLib .h"

2
3
4
5

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){32

第 4 章 入力関係処理の基礎
int white , posX , posY;

67// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}
SetDrawScreen ( DX_SCREEN_BACK );

8
9
10
11
12
13
14
15

// マウスを表示状態にする
SetMouseDispFlag ( TRUE );// 白色取得
white = GetColor ( 255, 255, 255 );

16
17
18
19
20

// E S C A P E キ ー が 押 さ れ る ま で プ ロ グ ラ ム を 続 行
while( CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {ClearDrawScreen ();if( ProcessMessage () == -1 ) {break;}// マウスポインタの座標を取得
GetMousePoint ( &posX , &posY );// 座標を表示
DrawFormatString ( 20, 20, white , " マウスの座標 ( %d, %d )", posX , posY );// 裏画面反映
ScreenFlip ();
}

21
22
23
24
25
26
27
28
29
30
31
32
33
34

// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

35
36
37
38

}

このプログラムの実行結果は，図 4.3 となります．

図 4.3: マウスの位置を表示するプログラム

4.4

マウスボタンの状態の取得

マウスのどのボタンが押されたかどうかを判定するには，GetMouseInput 関数を利用します．この関数の定義は以下のようになっています．

int GetMouseInput( void );戻り値にはマウスの入力状態が入っています．どのボタンが押されているかは，表 4.2の値との AND 演算を行い，結果が 0 でなければボタンが押されていると判定することが
できます．

33

4.4 マウスボタンの状態の取得
定義値

マウスボタン

MOUSE INPUT LEFT
MOUSE INPUT RIGHT
MOUSE INPUT MIDDLE

マウス左ボタン
マウス右ボタン
マウス中央ボタン

表 4.2: マウスの定義値
例えば，マウス左ボタンが押されているかを調べるには，GetMouseInput 関数の戻り値と MOUSE INPUT LEFT との AND 演算を行えばよいわけです．

if( ( GetMouseInput() & MOUSE_INPUT_LEFT ) != 0 ) {//左クリック時の処理}
では，前節のプログラムを改良して，左クリック時に文字列を表示するプログラムを作成してみましょう．
リスト 4.4: ”foundation-16.cpp”
1# include "DxLib .h"

2
3
4
5
6

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white , posX , posY;

7
8
9
10
11
12
13
14

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}
SetDrawScreen ( DX_SCREEN_BACK );

15
16
17
18
19

// マウスを表示状態にする
SetMouseDispFlag ( TRUE );// 白色取得
white = GetColor ( 255, 255, 255 );

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

// E S C A P E キ ー が 押 さ れ る ま で プ ロ グ ラ ム を 続 行
while( CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {ClearDrawScreen ();if( ProcessMessage () == -1 ) {break;}// マウスポインタの座標を取得
GetMousePoint ( &posX , &posY );// 座標を表示
DrawFormatString ( 20, 20, white , " マウスの座標 ( %d, %d )", posX , posY );// マウス左ボタンが押されているかどうか
if( ( GetMouseInput () & MOUSE_INPUT_LEFT ) != 0 ) {DrawString ( 20, 50, " マウス左ボタンが押されています ", white );}// 裏画面反映
ScreenFlip ();
}

38

34

第 4 章 入力関係処理の基礎// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

39
40
41
42

}

このプログラムの実行結果は図 4.4 となります．

図 4.4: マウスボタンの状態を取得するプログラム

35

37

第 5 章 サウンド取り扱いの基礎
この章では，音楽，効果音の再生方法の基礎について解説していきます．

5.1

サウンドの再生

WAV,MP3,OGG ファイルを再生するには，PlaySoundFile 関数を利用します．この関数の定義は以下のようになっています．
int PlaySoundFile( char *FileName , int PlayType );この関数は再生に成功すると戻り値として 0 を，失敗すると-1 を返してきます．第 1 引
数 Filename には音声ファイルのパスを指定します．第 2 引数 PlayType には音声の再生方法を指定します（表 5.1）．
定義

再生形式

DX PLAYTYPE NORMALDX PLAYTYPE BACKDX PLAYTYPE LOOP

通常再生
バックグラウンド再生
ループ再生

表 5.1: 音声再生方式
通常再生は，サウンド再生が終わるまで処理を停止します．それに対し，バックグラウ
ンド再生は再生を始めると同時に処理を返してきます．通常はバックグラウンド再生を利用します．ループ再生は，バックグラウンド再生とほとんど同じですが，再生を停止するまでループして音を再生します．
では，音楽を再生するプログラムをお見せします．今回利用した音声ファイルは loop3.mp3
です．
リスト 5.1: ”foundation-17.cpp”
1# include "DxLib.h"

2
3
4
5
6

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white;

7
8
9
10

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化

5.2 メモリ上にあるサウンドの再生
if( DxLib_Init () == -1 ) {return -1;
}

11
12
13
14

// 白色取得
white = GetColor (255 ,255 ,255);

15
16
17

// 音楽 loop3. m p 3 の 再 生
if( PlaySoundFile ( "loop3.mp3", DX_PLAYTYPE_LOOP ) == 0 ) {DrawString ( 30, 30, "loop3. m p 3 を 再 生 中 ", white );
}

18
19
20
21
22

WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

23
24
25
26
27

}

このプログラムの実行結果は図 5.1 となります．

図 5.1: 音楽の再生

5.2

メモリ上にあるサウンドの再生

前節で利用した PlaySoundFile 関数は，関数を呼び出した際にハードディスクから音声ファイルを読み込んできます．このため，PlaySoundFile 関数は高速な処理が求められるゲームには向いていません．今回は，メモリ上にサウンドをあらかじめロードしておき，必要に応じて再生するようにしましょう．
音声ファイルをメモリ上に読み込むには LoadSoundMem 関数を利用します．この関数の定義は以下のようになっています．

int LoadSoundMem( char *FileName );FileName には音声ファイルのパスを指定します．この関数は，音声ファイル読み込み成功時には，戻り値としてサウンドハンドルと呼ばれるサウンド認識番号を返してきます．
メモリ上に読み込んだ音声を再生するには，PlaySoundMem 関数を利用します．
int PlaySoundMem( int SoundHandle , int PlayType );第 1 引数 SoundHandle には，サウンド認識番号を渡します．第 2 引数 PlayType には，表5.1 の音声再生形式を渡します．

38

第 5 章 サウンド取り扱いの基礎
では，これらの関数を利用して，音楽及び効果音を再生するプログラムをお見せします．
A,B,C いずれかのキーを押すと，効果音 hapyou1.wav, hapyou2.wav, toujyo.wav が再生されます．また，常に loop3.mp3 がバックグラウンドミュージックとして再生されています．
リスト 5.2: ”foundation-18.cpp”
1# include "DxLib.h"

2
3
4
5
6
7
8
9
10

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white;
int music;
int soundA , soundB , soundC ;// キー状態
char keyStatus [ 256 ];11// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

12
13
14
15
16
17
18

// 白色取得
white = GetColor (255 ,255 ,255);

19
20
21

// 音楽読み込み
music = LoadSoundMem ( "loop3.mp3" );// サウンド読み込み
soundA = LoadSoundMem ( " hapyou1 .wav" );soundB = LoadSoundMem ( " hapyou2 .wav" );soundC = LoadSoundMem ( " toujyo .wav" );

22
23
24
25
26
27
28

// 音楽再生PlaySoundMem ( music , DX_PLAYTYPE_LOOP );

29
30
31

while( 1 ) {if( ProcessMessage () == -1 ) {break;}// キーの状態を取得
GetHitKeyStateAll ( keyStatus );

32
33
34
35
36
37
38

//A, B, C キ ー で サ ウ ン ド 再 生
if( keyStatus [ KEY_INPUT_A ] ) {PlaySoundMem ( soundA , DX_PLAYTYPE_BACK );}else if ( keyStatus [ KEY_INPUT_B ] ) {PlaySoundMem ( soundB , DX_PLAYTYPE_BACK );}else if ( keyStatus [ KEY_INPUT_C ] ) {PlaySoundMem ( soundC , DX_PLAYTYPE_BACK );
}

39
40
41
42
43
44
45
46
47

DrawString ( 20, 20, "A, B, C キ ー で サ ウ ン ド 再 生 ", white );

48

}

49
50

WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

51
52
53
54
55

}

音声ファイルのため，実行結果は省略します．

39

5.3 サウンドデータの音量調節

5.3

サウンドデータの音量調節

音声データの音量を設定するには ChangeVolumeSoundMem 関数を利用します．この関数の定義は以下のようになっています．

int ChangeVolumeSoundMem( int VolumePal, int SoundHandle );第 1 引数 VolumePal には音量 (最小値 0, 最大値 255) を渡します．第 2 引数にはサウンド
識別番号（サウンドハンドル）を渡します．
では，この関数を利用して，音楽のボリューム調節を行うプログラムの例をお見せします．上ボタンでボリュームアップ，下ボタンでボリュームダウンを行います．
リスト 5.3: ”foundation-19.cpp”
1# include "DxLib .h"

2
3
4
5
6
7
8
9
10
11

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int white;
int music;// キー状態
char keyStatus [ 256 ];// ボリューム (0 〜 255)
int volume = 100;

12
13
14
15
16
17
18
19
20
21
22

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// D X ラ イ ブ ラ リ 初 期 化
if( DxLib_Init () == -1 ) {return -1;}// バックバッファへの描画
SetDrawScreen ( DX_SCREEN_BACK );// 白色取得
white = GetColor (255 ,255 ,255);

23
24
25
26
27

// 音楽読み込み
music = LoadSoundMem ( "loop3.mp3" );// 音楽再生PlaySoundMem ( music , DX_PLAYTYPE_LOOP );

28
29
30
31
32
33
34
35

while( 1 ) {ClearDrawScreen ();if( ProcessMessage () == -1 ) {break;}// キーの状態を取得
GetHitKeyStateAll ( keyStatus );

36
37
38
39
40
41
42
43
44
45
46
47

// 上，下ボタンでボリューム調節if( keyStatus [ KEY_INPUT_UP ] ) {// ボリュームを上げる
volume ++;if( volume > 255 ) {volume = 255;}// ボリューム設定
ChangeVolumeSoundMem ( volume , music );}else if ( keyStatus [ KEY_INPUT_DOWN ] ) {// ボリュームを下げる40

第 5 章 サウンド取り扱いの基礎
volume --;if( volume < 0 ) {volume = 0;}// ボリューム設定
ChangeVolumeSoundMem ( volume , music );

48
49
50
51
52
53

}
DrawFormatString ( 20, 20, white , " ボリューム %d( 最 小 0 , 最 大 255) ", volume );ScreenFlip ();

54
55
56

}

57
58

WaitKey ();// D X ラ イ ブ ラ リ 終 了 処 理
DxLib_End ();return 0;

59
60
61
62
63

}

このプログラムの実行結果は図 5.2 となります．

図 5.2: 音量の調節

41

第 II 部

ノベルゲームの作成

45

第 6 章 サウンドノベル風メッセージ表示
サウンドノベル風にメッセージを表示するプログラムを作っていきたいと思います．サ
ウンドノベル風メッセージとは，画面に一文字づつ文字を描画していくものです．完成イ
メージは図 6.1 です．

図 6.1: サウンドノベル風メッセージ表示

いきなり GUI で作り始めるのは大変なので，まずは CUI で部品を作っていき，その後
GUI に移植していきます．

6.1 指定範囲の英字文字列を表示

6.1

指定範囲の英字文字列を表示

まずは指定範囲の文字列を表示するプログラムを作成してみましょう．これは，メッセー
ジを複数行表示するときに使用します．
例えば，表示したいメッセージが 50 文字あった場合，すべてを 1 行で表示すると画面からはみ出してしまいます．コンソールで文字を表示する時とは違い，DX ライブラリで文字列を表示するときは，改行などの処理は自分で行わなければなりません．よって，1
行目を 1 文字目から 25 文字目まで表示させ，2 行目は 26 文字目から 50 文字目まで表示するといった作業が必要となります．こういったときに利用する部品を作っていきます．
では，まずは次のような関数を作ってみます．
リスト 6.1: ”message-cui-01.cpp”
1
2# include <stdio .h># include <string .h>345// 表示したい文字列
char g_message [256] = " HelloWorld ";

6
7
8
9
10
11
12

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len){int i;// 文字数
int maxLen = strlen ( message );13// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;
}

14
15
16
17
18

// 指定した位置から l e n 文 字 分 表 示 す る
for( i = 0; i < len && message [ start + i ] != ’\0 ’; i++ ) {printf ("%c", message [ start + i ] );}printf ("\n");

19
20
21
22
23
24

}

25
26
27
28
29
30
31
32
33
34
35
36
37

int main (){// g _ m e s s a g e を 表 示 す る
writeSubstring (g_message , 0, strlen ( g_message ) );// g_message 2 文 字 目 ( g_message [1] ) か ら 5 文 字 分 表 示 す る
writeSubstring (g_message , 1, 5);// g_message 6 文 字 目 ( g_message [5] ) か ら 10 文 字 分 表 示 す る//10 文 字 は 表 示 で き な い の で ， 文 字 列 の 最 後 ま で 表 示
writeSubstring (g_message , 5, 10);// g_message 20 文 字 目 か ら 表 示 （ な に も 表 示 さ れ な い ）writeSubstring (g_message , 20, 10);}

writeSubstring 関数は message で渡した文字列の start から len 文字分表示する関数です．
main 関数でどのような動作をするか確認してみましょう．実行結果は以下のようになりました．

46

第 6 章 サウンドノベル風メッセージ表示
¶
実行結果

³

HelloWorld
elloW
World

µ

´

さて，writeSubstring 関数が正しく動作しているか確認していきましょう．
実行結果 1 行目は g message をただ表示するものです．よって HelloWorld が表示されています．この関数で気をつけなければならないことは，1 文字目が 0 の位置から始まることです．
2 行目は 2 文字目から 5 文字文表示するものです．うまくいっていますね．3 行目は 5文字目から 10 文字分表示するものです．といっても，HelloWorld の 6 文字目から 10 文
字も文字が存在しません．よって 6 文字目から文字列の最後まで表示しています．最後のwriteSubstring 関数は 20 文字目から 10 文字分表示するものですが，HelloWorld は 20 文字もないので，画面には何も表示されません．

6.2

指定範囲の日本語文字列を表示

さて，前節の writeSubstring 関数ですが，実は日本語を表示しようとするとうまくいきません．
ここで文字コードの問題が浮上します．日本語は char1 文字（1byte) だけでは表現することができないのです．よって，char 2 文字で表現しているのです．ただし，これは文字コードが SHIFT JIS の場合です1 ．ほかの文字コードでは，もしかしたら日本語を 3 バイ
ト (char3 つ分）で表しているかもしれません．
文字コードの問題はとても厄介です．著者も Windows プログラムを Linux 用に移植した
時に，文字コードの問題によく悩まされたものです．まあ，ここでは Windows でゲームを作るので，文字コードは SHIFT JIS としましょう．
では，前節のプログラムを日本語に対応させてみましょう．
リスト 6.2: ”message-cui-02.cpp”
1
2# include <stdio.h># include <string .h>

3
4
5
6

// 注意点：文字コードは S H I F T _ J I S （ W i n d o w s 標 準 ） を 前 提 と し て い る//
他の文字コードでは正常に動作しない// S H I F T _ J I S の 場 合 ， 日 本 語 は 2 バ イ ト で 表 さ れ る

789// 表示したい文字列
char g_message [256] = " はろーわーるど ";

10
11
12
13
14
15
16

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len){int i;// 文字数
int maxLen = strlen ( message );17
1

SHIFT JIS は Windows 標準の文字コードです.

476.3 日本語文字の判定方法// 日本語の場合，位置を 2 倍 す る
start *= 2;
len *= 2;

18
19
20
21

// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;
}

22
23
24
25
26

// 指定した位置から l e n 文 字 分 表 示 す る
for( i = 0; i < len && message [ start + i ] != ’\0 ’; i += 2 ) {printf ("%c", message [ start + i ] );printf ("%c", message [ start + i + 1 ]);}printf ("\n");

27
28
29
30
31
32
33

}

34
35
36
37
38
39
40
41
42
43
44
45
46

int main (){// g _ m e s s a g e を 表 示 す る
writeSubstring (g_message , 0, strlen ( g_message ) );// g_message 2 文 字 目 か ら 5 文 字 分 表 示 す る
writeSubstring (g_message , 1, 5);// g_message 6 文 字 目 か ら 10 文 字 分 表 示 す る//10 文 字 は 表 示 で き な い の で ， 文 字 列 の 最 後 ま で 表 示
writeSubstring (g_message , 5, 10);// g_message 20 文 字 目 か ら 表 示 （ な に も 表 示 さ れ な い ）writeSubstring (g_message , 20, 10);}

実行させて正しく動作するかどうか見てみましょう．
¶
実行結果

³

はろーわーるど
ろーわーる
るど
µ

´

どうやらうまく動いているようです．

6.3

日本語文字の判定方法

前節で日本語文字列を切り出すことができるようになりました．しかし，ここで新たな問題が浮上してしまいました．それは，日本語と英文字が混ざっている場合です．
よって，いまから表示する文字が日本語かどうかを判定する必要があるわけです．もし，
日本語かどうかが判定できれば，英字の場合はインデクスを 1 進め，日本語の場合はインデクスを 2 進めればよいわけです．
SHIFT JIS の場合，日本語は 2 バイトで表現しているわけですが，実は 1 バイト目を調べるだけで日本語（2 バイト文字）なのかそうでないのかが判定できる仕組みになっています．
SHIFT JIS の 2 バイト文字の場合，第一バイトが 0x81〜0x9f または 0xe0〜0xfc の範囲にあります．よって，次のようなコードで指定された char 文字が日本語かそうでないのかを判定できます．

48

第 6 章 サウンドノベル風メッセージ表示//code が日本語であるか判定する//戻り値 1:日本語 0:日本語ではない
int isJapaneseCharacter(unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

1
2
3
4
5
6
7
8
9
10

では次のようなコードを書いて正しく動作するかどうか確認していきましょう．
リスト 6.3: ”message-cui-03.cpp”
1
2# include <stdio.h># include <string .h>34// S H I F T _ J I S の 場 合 ， 上 位 バ イ ト が 0 x 8 1 〜 0 x 9 F 、 0 x E 0 〜 0 x F C の 範 囲 に 収 ま る

5
6
7
8
9
10
11
12
13
14
15

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

16
17
18
19
20
21

int main (){char check_first [3] = " え ";char check_second [3] = " 下 ";char check_third [3] = "h";22

printf (" 「え」の場合 , %d\n", isJapaneseCharacter ( check_first [0] ) );printf (" 「下」の場合， %d\n", isJapaneseCharacter ( check_second [0] ) );printf (" 「 h 」 の 場 合 ， % d\n", isJapaneseCharacter ( check_third [0] ) );

23
24
25
26

return 0;

27
28

}

¶
実行結果

³

「え」の場合, 1「下」の場合，1「h 」の場合，0µ´

どうやらうまく判定できているようです．次節ではこの関数を使って writeSubstring 関数を改良していきましょう．

49

6.4 指定範囲の文字列を表示

6.4

指定範囲の文字列を表示

やっと指定範囲の文字列を表示させる準備が整いました．改良した writeSubstring 関数
は英字，日本語文字が混ざっていても正しく表示させることができます．表示させる文字
列は，
はろー Hello わーるど World
です．コードは次のようになりました．
リスト 6.4: ”message-cui-04.cpp”
1
2# include <stdio .h># include <string .h>

3
4
5
6
7

// 注意点：文字コードは S H I F T _ J I S （ W i n d o w s 標 準 ） を 前 提 と し て い る//
他の文字コードでは正常に動作しない// S H I F T _ J I S の 場 合 ， 日 本 語 は 2 バ イ ト で 表 さ れ る// 上位バイトが 0 x 8 1 〜 0 x 9 F 、 0 x E 0 〜 0 x F C の 範 囲 に 収 ま る

8910// 表示したい文字列
char g_message [256] = " はろー H e l l o わ ー る ど W o r l d ";

11
12
13
14
15
16
17
18
19
20
21
22

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

23
24
25
26
27
28
29
30

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len){int i;// 文字数
int maxLen = strlen ( message );

31
32
33
34
35
36
37
38
39
40
41
42
43
44

// s t a r t の 位 置 を 変 更 す る// s t a r t の 位 置 ま で に 日 本 語 が で て き て い た ら ， 1 を 足 し て い く
for( i = 0; i < start && message [i] != ’\0’; ) {if( isJapaneseCharacter ( message [i] ) ) {// 日本語の場合， 2 バ イ ト 分 す す め る
i += 2;// s t a r t に 1 バ イ ト 分 足 すstart ++;
}else {// 半角文字の場合， 1 バ イ ト 分 進 め る
i++;
}
}

45
46
47
48
49

// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;}50
51// 指定した位置から l e n 文 字 分 表 示 す る

50

第 6 章 サウンドノベル風メッセージ表示
for( i = 0; i < len && message [ start + i ] != ’\0 ’; ) {if( isJapaneseCharacter ( message [ start + i ] ) ) {// 日本語の場合， 2 文 字 分 表 示 す る
printf ("%c", message [ start + i ] );printf ("%c", message [ start + i + 1 ]);// l e n は 日 本 語 な の で ， 1 バ イ ト 分 追 加 す る
len ++;//2 バ イ ト 分 進 め る
i += 2;}else {// 半角文字 1 文 字 を 表 示
printf ("%c", message [ start + i ] );//1 バ イ ト 分 進 め る
i++;}}printf ("\n");

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

}

70
71
72
73
74
75
76
77
78
79
80
81
82

int main (){// g _ m e s s a g e を 表 示 す る
writeSubstring (g_message , 0, strlen ( g_message ) );// g_message 2 文 字 目 か ら 5 文 字 分 表 示 す る
writeSubstring (g_message , 1, 5);// g_message 6 文 字 目 か ら 30 文 字 分 表 示 す る//30 文 字 は 表 示 で き な い の で ， 文 字 列 の 最 後 ま で 表 示
writeSubstring (g_message , 5, 30);// g_message 20 文 字 目 か ら 表 示 （ な に も 表 示 さ れ な い ）writeSubstring (g_message , 20, 10);
}

¶
実行結果

³

はろー Hello わーるど World
ろー Helllo わーるど Worldµ´

ようやく完成しました．うまく動作していますね．

6.5

指定範囲の文字列を表示 GUI 版

ここからは DX ライブラリを使って画面に文字を表示させていきましょう．前節のプログラムを GUI に移植してみましょう．文字列を表示させるには DrawString 関数を利用します．

int DrawString( int x , int y , char *String , int Color );描画する文字列の左上の座標を (x,y), 表示したいメッセージは String，メッセージの色はColor となっています．
では，「はろー hello わーるど」という文字列を 1 文字目から 5 文字分表示するプログラ
ムを作成してみましょう．なんてことはありません．前節のプログラムを少し書き換えた
だけです．

51

6.5 指定範囲の文字列を表示 GUI 版
リスト 6.5: ”message-gui-01.cpp”
1# include "DxLib .h"

2
3
4
5
6

// 注意点：文字コードは S H I F T _ J I S （ W i n d o w s 標 準 ） を 前 提 と し て い る//
他の文字コードでは正常に動作しない// S H I F T _ J I S の 場 合 ， 日 本 語 は 2 バ イ ト で 表 さ れ る// 上位バイトが 0 x 8 1 〜 0 x 9 F 、 0 x E 0 〜 0 x F C の 範 囲 に 収 ま る

789

int isJapaneseCharacter ( unsigned char code );void writeSubstring (char* message , int start , int len , int posX , int posY , int color );

10
11
12
13
14
15
16

// メッセージのフォントの大きさ# define MESSAGE_FONT_SIZE 20// 仮想バッファの最大文字数# define MESSAGE_MAX_LENGTH 30// 仮想バッファの最大行数# define MESSAGE_MAX_LINE 5

17
18
19

// 表示したいメッセージ
char g_message [ MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ] = "はろー h e l l o わ ー る ど ";

20
21
22

// 画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer [ MESSAGE_MAX_LINE ][ MESSAGE_MAX_LENGTH ];

23
24
25
26
27
28
29
30
31
32
33
34

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

35
36
37
38
39
40
41
42
43

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る// 文字列左側の座標は (posX , posY), 文 字 の 色 を c o l o r と す るvoid writeSubstring (char* message , int start , int len , int posX , int posY , int color ){int i;// 文字数
int maxLen = strlen ( message );

44
45
46
47
48
49
50
51
52
53
54
55
56
57

// s t a r t の 位 置 を 変 更 す る// s t a r t の 位 置 ま で に 日 本 語 が で て き て い た ら ， 1 を 足 し て い く
for( i = 0; i < start && message [i] != ’\0’; ) {if( isJapaneseCharacter ( message [i] ) ) {// 日本語の場合， 2 バ イ ト 分 す す め る
i += 2;// s t a r t に 1 バ イ ト 分 足 すstart ++;
}else {// 半角文字の場合， 1 バ イ ト 分 進 め る
i++;
}
}

58
59
60
61
62

// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;}63
64// 指定した位置から l e n 文 字 分 表 示 す る

52

第 6 章 サウンドノベル風メッセージ表示
for( i = 0; i < len && message [ start + i ] != ’\0 ’; ) {if( isJapaneseCharacter ( message [ start + i ] ) ) {// 日本語の場合， 2 文 字 分 表 示 す る
g_messageBuffer [0][ i ] = message [ start + i ];g_messageBuffer [0][ i + 1 ] = message [ start + i + 1 ];// l e n は 日 本 語 な の で ， 1 バ イ ト 分 追 加 す る
len ++;//2 バ イ ト 分 進 め る
i += 2;}else {// 半角文字 1 文 字 を 表 示// printf ("%c", message [ start + i ] );g_messageBuffer [0][ i ] = message [ start + i ];//1 バ イ ト 分 進 め る
i++;}}g_messageBuffer [0][i] = ’\0’;

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

// メッセージ描画
DrawString (posX , posY , g_messageBuffer [0] , color );

84
85
86

}

87
88
89
90
91
92
93
94
95
96
97
98

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

99// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

100
101
102

int whiteColor = GetColor (255 ,255 ,255);

103
104

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {// g _ m e s s a g e の 文 字 を 1 文 字 目 か ら 5 文 字 分 表 示 す る// 文字列描画の位置は (50, 100) , 色 は 白 と す る
writeSubstring ( g_message , 0, 5, 50 ,100 , whiteColor );Sleep( 100 );ScreenFlip ();
}

105
106
107
108
109
110
111
112
113

DxLib_End ();return 0;

114
115
116

}

g messageBuffer は画面にメッセージを表示する文字をためておくものです．今回は g messageBuffer[0]，つまり 1 行目までしか利用していないわけですが，今後メッセージが 2 行,3 行に渡って表示するときに g messageBuffer[1], g messageBuffer[2] を利用していきます．
このプログラムの実行結果は，図 6.2 となります．

53

6.5 指定範囲の文字列を表示 GUI 版

図 6.2: 指定した範囲の文字列を表示

54

第 6 章 サウンドノベル風メッセージ表示

6.6

メッセージを 1 文字ずつ表示

メッセージを 1 文字ずつ画面に表示させてみましょう．特に難しい処理はしていません．
ただ，最初は 1 文字だけ表示，約 100ms 後に 2 文字目まで表示，さらに 100ms 後に 3 文
字目まで表示 と繰り返しているだけです．
リスト 6.6: ”message-gui-02.cpp”
1# include "DxLib.h"

2
3
4

int isJapaneseCharacter ( unsigned char code );void writeSubstring (char* message , int start , int len , int posX , int posY , int color );

5
6
7
8
9
10
11

// メッセージのフォントの大きさ# define MESSAGE_FONT_SIZE 20// 仮想バッファの最大文字数# define MESSAGE_MAX_LENGTH 30// 仮想バッファの最大行数# define MESSAGE_MAX_LINE 5

12
13
14

// 表示したいメッセージ
char g_message [ MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ] = " はろー h e l l o わ ー る ど ";

15
16
17

// 画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer [ MESSAGE_MAX_LINE ][ MESSAGE_MAX_LENGTH ];

18
19
20
21
22
23
24
25

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){// 省略（前回と同じ）
}

26
27
28
29
30
31
32
33

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る// 文字列左側の座標は (posX , posY), 文 字 の 色 を c o l o r と す るvoid writeSubstring (char* message , int start , int len , int posX , int posY , int color ){// 省略（前回と同じ）
}

34
35
36
37
38
39
40
41
42
43
44
45

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

46
47
48

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

49
50
51
52

// 現在何文字目までを表示しているか
int currentCursor = 0;
int whiteColor = GetColor (255 ,255 ,255);

53
54
55
56

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {if( g_message [ currentCursor ] != ’\0’ ) {55

6.7 改行処理を加えたメッセージ描画
currentCursor ++;

57

}

58
59

writeSubstring ( g_message , 0, currentCursor , 50 ,100 , whiteColor );Sleep( 100 );ScreenFlip ();

60
61
62

}

63
64

DxLib_End ();return 0;

65
66
67

}

メインループ内で currentCursor という変数を利用していますが，この値ははじめ 0 にセットされており，時間が立つにつれて 1,2,3 と増えていきます．g message[ currentCursor] がナル文字 (\0) と等しくなるまでこの増加を繰り返します．また，文字描画部分では，0文字目から currentCursor 文字目までを画面に描画しています．このような処理をすること
で，画面に 1 文字ずつ文字が描画しているようにみえるわけです．

6.7

改行処理を加えたメッセージ描画

さて，前節のプログラムでやっとサウンドノベル風のメッセージ描画ができたわけですが，1 つ問題があります．それは，メッセージが長すぎる場合に画面からはみ出してしまうことです．この問題を解決するために，改行処理を加えて見ましょう．
まずは writeSubstring 関数に少し手を加えましょう．関数の宣言を以下のように変更しました．

void writeSubstring(char* message, int start, int len,int posX, int posY, int color, int bufferLine)bufferLine という引数を一つ増やしました．これは，いま何行目の文字列を描画しているかを表すものです．
さて，メインループの中身も書き換えて見ましょう．どのような処理を行ったらよいでしょうか．ここで，メッセージが，例えば 70 文字あったとしましょう．また，1 行で表すことができる文字数は 30 とします．まず，1 行目を描画しているときは 1 文字目から 30 文
字分を 1 文字ずつ g messageBuffer[0] に格納し，画面に描画します．つぎに 31 文字目の描画に入るわけですが，そのときは 1 行目にはもう文字を追加することができません．よって，g messageBuffer[1] に今度は 1 文字ずつ文字を格納していくわけです．3 行目の時も同様に処理を行います．
さて，これらを考慮してプログラムを組むと次のようになりました．
リスト 6.7: ”message-gui-03.cpp”
1# include "DxLib .h"

2
3
4
5

int isJapaneseCharacter ( unsigned char code );void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine );67// メッセージのフォントの大きさ56

第 6 章 サウンドノベル風メッセージ表示

8
9
10
11
12

# define MESSAGE_FONT_SIZE 20// 仮想バッファの最大文字数# define MESSAGE_MAX_LENGTH 30// 仮想バッファの最大行数# define MESSAGE_MAX_LINE 5

13
14
15
16
17

// 表示したいメッセージ
char g_message [ MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ] =" はろー h e l l o わ ー る ど W o r l d あ い う え お か き く け こ さ し す せ そ た ち つ て と な " \" にぬねのはひふへほまみむめもやゆよらりるれろわをん";

18
19
20

// 画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer [ MESSAGE_MAX_LINE ][ MESSAGE_MAX_LENGTH ];

21
22
23
24
25
26
27
28
29
30
31
32

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

33
34
35
36
37
38
39
40
41

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine ){int i;// 文字数
int maxLen = strlen ( message );

42
43
44
45
46
47
48
49
50
51
52
53
54
55

// s t a r t の 位 置 を 変 更 す る// s t a r t の 位 置 ま で に 日 本 語 が で て き て い た ら ， 1 を 足 し て い く
for( i = 0; i < start && message [i] != ’\0 ’; ) {if( isJapaneseCharacter ( message [i] ) ) {// 日本語の場合， 2 バ イ ト 分 す す め る
i += 2;// s t a r t に 1 バ イ ト 分 足 すstart ++;
}else {// 半角文字の場合， 1 バ イ ト 分 進 め る
i++;
}
}

56
57
58
59
60

// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;
}

61
62
63
64
65
66
67
68
69
70
71
72

// 指定した位置から l e n 文 字 分 表 示 す る
for( i = 0; i < len && message [ start + i ] != ’\0 ’; ) {if( isJapaneseCharacter ( message [ start + i ] ) ) {// 日本語の場合， 2 文 字 分 b u f f e r に セ ッ ト
g_messageBuffer [ bufferLine ][ i ] = message [ start + i ];g_messageBuffer [ bufferLine ][ i + 1 ] = message [ start + i + 1 ];// l e n は 日 本 語 な の で ， 1 バ イ ト 分 追 加 す る
len ++;//2 バ イ ト 分 進 め る
i += 2;}else {

57

6.7 改行処理を加えたメッセージ描画// 半角文字 1 文 字 を セ ッ ト
g_messageBuffer [ bufferLine ][ i ] = message [ start + i ];//1 バ イ ト 分 進 め る
i++;

73
74
75
76

}
}
g_messageBuffer [ bufferLine ][i] = ’\0’;

77
78
79
80

// メッセージ描画
DrawString (posX , posY , g_messageBuffer [ bufferLine ], color );

81
82
83

}

84
85
86
87
88
89
90
91
92
93
94
95

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

96
97
98

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;99
100

int i;

101
102
103
104
105
106
107

// 現在何文字目までを表示しているか
int currentCursor = 0;// 何行目の文字を表示しているか
int currentLineCursor = 0;// 白
int whiteColor = GetColor (255 ,255 ,255);

108
109
110
111
112
113

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {if( g_message [ currentCursor ] != ’\0 ’ ) {currentCursor ++;
}

114
115
116

// 画面クリア
ClearDrawScreen ();

117
118
119
120
121
122
123

// MESSAGE_MAX_LENGTH ま で 文 字 を 描 画 し た ら 段 落 を 切 り 替 え る
if( currentCursor % MESSAGE_MAX_LENGTH == 0 ) {if( g_message [ currentCursor ] != ’\0 ’ ) {currentLineCursor ++;
}
}

124
125
126
127
128
129
130
131
132
133
134
135
136
137

for( i = 0; i < MESSAGE_MAX_LINE ; i++ ) {if( i == currentLineCursor ) {// サウンドノベルメッセージ風に表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH ,currentCursor - MESSAGE_MAX_LENGTH * i,
50, 100 + MESSAGE_FONT_SIZE * i, whiteColor , i );break;}else {// メッセージをそのまま表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH , MESSAGE_MAX_LENGTH , 50,
100 + MESSAGE_FONT_SIZE * i, whiteColor , i );}}58

第 6 章 サウンドノベル風メッセージ表示
Sleep( 100 );ScreenFlip ();

138
139

}

140
141

DxLib_End ();return 0;

142
143

}

144

メインループ内の currentLineCursor は現在描画中のメッセージの行番号を表しています．段落を切り替える際に currentCursor % MESSAGE_MAX_LENGTH という処理をしています．これは，currentCursor が MESSAGE_MAX_LENGTH で割り切れた時に if 文を実行することを表しています．具体的には，MESSAGE_MAX_LENGTH は 30 なわけですが，現在描画中の文字が 30 の倍数番目に差し掛かったときに改行するということです．よって，30
文字目，60 文字目，90 文字目の時に改行処理を行います．
メッセージ描画部分ですが，途中以下のような処理をしているところがあります．
1
2
3
4
5
6
7
8
9
10
11
12
13
14

for( i = 0; i < MESSAGE_MAX_LINE; i++ ) {if( i == currentLineCursor ) {//サウンドノベルメッセージ風に表示
writeSubstring( g_message, i * MESSAGE_MAX_LENGTH ,currentCursor - MESSAGE_MAX_LENGTH * i,
50, 100 + MESSAGE_FONT_SIZE * i, whiteColor, i );break;}else {//メッセージをそのまま表示
writeSubstring( g_message, i * MESSAGE_MAX_LENGTH ,MESSAGE_MAX_LENGTH, 50,
100 + MESSAGE_FONT_SIZE * i, whiteColor, i );}}これは，これから表示する 1 文字（つまり currentCursor 番目の文字）が currentLineCursorと同じ行にある場合，サウンドノベル風にメッセージを表示させ，そうでない場合は，その行のメッセージをそのまま画面に表示させます．
さて，このプログラムの実行結果は 6.3 となりました．ちょっと見た目に問題があります．それは日本語文字の幅と英文字の幅が違うため，文字の折り返しを行う場所が 1 行目と 2 行目でずれてしまっています．これを修正するためにはいろいろと厄介な処理が必要なので，これで妥協するとしましょう．気に食わない方は，是非この問題を修正してみて
ください．

6.8

メッセージボックスの表示

メッセージをメッセージボックスの画像の上に表示させてみましょう．また，前回のプ
ログラムはメインループの中にいろいろと処理を書きすぎました．よって，これらの処理

59

6.8 メッセージボックスの表示

図 6.3: 改行処理を加えたメッセージ描画60

第 6 章 サウンドノベル風メッセージ表示
を関数に分割していきたいと思います．関数に分割するにあたって，メインループ内に存在していたローカル変数，例えば currentCursor 変数などをグローバル変数にする必要もあります．
プログラムは以下のようになりました．
リスト 6.8: ”message-gui-04.cpp”
1# include "DxLib.h"

2
3
4
5
6
7

int isJapaneseCharacter ( unsigned char code );void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine );void drawMessage ();void initGame ();

8
9
10
11
12
13
14
15
16
17
18
19
20

// メッセージのフォントの大きさ# define MESSAGE_FONT_SIZE 20// 仮想バッファの最大文字数# define MESSAGE_MAX_LENGTH 30// 仮想バッファの最大行数# define MESSAGE_MAX_LINE 5// メッセージボックスの X 座 標# define MESSAGE_BOX_X_POS 40// メッセージボックスの Y 座 標# define MESSAGE_BOX_Y_POS 340// メッセージボックスの画像ファイル# define MESSAGE_BOX_GRAPHIC_FILENAME "./ Pic/boxd3.jpg"

21
22
23
24
25

// 表示したいメッセージ
char g_message [ MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ] =" はろー h e l l o わ ー る ど W o r l d あ い う え お か き く け こ さ し す せ そ た ち つ て と な " \" にぬねのはひふへほまみむめもやゆよらりるれろわをん";

26
27
28

// 画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer [ MESSAGE_MAX_LINE ][ MESSAGE_MAX_LENGTH ];29
30// メッセージボックス関係

31
32
33
34
35
36
37
38
39
40
41

// 現在何文字目までを表示しているか
static int g_currentCursor = 0;// 何行目の文字を表示しているか
static int g_currentLineCursor = 0;// 白static int g_whiteColor ;// 黒static int g_blackColor ;// メッセージボックスの画像
static int g_messageBoxGraphicHandle ;

42
43
44
45
46
47
48
49

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){// 省略（前回と同じ）
}

50
51
52
53
54
55
56
57

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine ){// 省略（前回と同じ）}

61

6.8 メッセージボックスの表示
58
59
60
61
62
63

// メッセージ描画
void drawMessage (){int i;

64// メッセージボックス描画
DrawGraph ( MESSAGE_BOX_X_POS , MESSAGE_BOX_Y_POS , g_messageBoxGraphicHandle , FALSE );

65
66
67

if( g_message [ g_currentCursor ] != ’\0 ’ ) {g_currentCursor ++;
}

68
69
70
71

// MESSAGE_MAX_LENGTH ま で 文 字 を 描 画 し た ら 段 落 を 切 り 替 え る
if( g_currentCursor % MESSAGE_MAX_LENGTH == 0 ) {if( g_message [ g_currentCursor ] != ’\0 ’ ) {g_currentLineCursor ++;
}
}

72
73
74
75
76
77
78

// メッセージ描画部分
for( i = 0; i < MESSAGE_MAX_LINE ; i++ ) {if( i == g_currentLineCursor ) {// メッセージ風に表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH ,g_currentCursor - MESSAGE_MAX_LENGTH * i,
MESSAGE_BOX_X_POS + 15,
MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15,
g_blackColor , i );break;}else {// メッセージをそのまま表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH ,MESSAGE_MAX_LENGTH , MESSAGE_BOX_X_POS + 15,
MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15,
g_blackColor , i );
}
}

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

}

98
99
100
101
102
103
104
105
106
107
108

// 初期化処理
void initGame (){// 白
g_whiteColor = GetColor (255 ,255 ,255);// 黒g_blackColor = GetColor (0, 0, 0);// メッセージボックス
g_messageBoxGraphicHandle = LoadGraph ( MESSAGE_BOX_GRAPHIC_FILENAME );
}

109
110
111
112
113
114
115
116
117
118
119
120
121

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

122

62

第 6 章 サウンドノベル風メッセージ表示// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

123
124
125

// 初期化処理
initGame ();

126
127
128

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

129
130
131

// 画面クリア
ClearDrawScreen ();

132
133
134

// メッセージ描画
drawMessage ();

135
136
137

Sleep( 100 );ScreenFlip ();

138
139

}

140
141

DxLib_End ();return 0;

142
143
144

}

処理を分割した以外は特に前回と変わっていません．このプログラムの実行結果は図 6.4となります．

図 6.4: メッセージボックスの上にメッセージを表示

63

6.9 サウンドノベル風メッセージ表示プログラム

6.9

サウンドノベル風メッセージ表示プログラム

やっとサウンドノベル風メッセージ表示プログラムの完成です．前節のプログラムでほぼ完成したようなものですが，ここで少し手を加えてみます．
表示したい文字をボタンを押すごとに変えるプログラムを作ってみたいと思います．いままでのプログラムは g message に格納された文字を描画していただけわけですが，このg message の中身を書き換えれば，思い通りのメッセージを画面に表示させることができます．今回は F1, F2, F3 キーを押すとメッセージが切り替わるようにしましょう．
また，描画したいメッセージが存在しない場合はメッセージボックスを表示しないようにしてみます．
プログラムは以下のようになりました．今回は関数の中身も省略せずに書いています．
少々長いですが，頑張って読んでみてください．
リスト 6.9: ”message-gui-05.cpp”
1# include "DxLib .h"

2
3
4
5
6

// 注意点：文字コードは S H I F T _ J I S （ W i n d o w s 標 準 ） を 前 提 と し て い る//
他の文字コードでは正常に動作しない// S H I F T _ J I S の 場 合 ， 日 本 語 は 2 バ イ ト で 表 さ れ る// 上位バイトが 0 x 8 1 〜 0 x 9 F 、 0 x E 0 〜 0 x F C の 範 囲 に 収 ま る

7
8
9
10
11
12

int isJapaneseCharacter ( unsigned char code );void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine );void drawMessage ();void initGame ();

13
14
15
16
17
18
19
20
21
22
23
24
25

// メッセージのフォントの大きさ# define MESSAGE_FONT_SIZE 20// 仮想バッファの最大文字数# define MESSAGE_MAX_LENGTH 30// 仮想バッファの最大行数# define MESSAGE_MAX_LINE 5// メッセージボックスの X 座 標# define MESSAGE_BOX_X_POS 40// メッセージボックスの Y 座 標# define MESSAGE_BOX_Y_POS 340// メッセージボックスの画像ファイル# define MESSAGE_BOX_GRAPHIC_FILENAME "./ Pic/boxd3 .jpg"

26
27
28

// 表示したいメッセージ
char g_message [ MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ];

29
30
31

// 画面にメッセージを表示する際にしようする仮想テキストバッファ
char g_messageBuffer [ MESSAGE_MAX_LINE ][ MESSAGE_MAX_LENGTH ];32
33// メッセージボックス関係

34
35
36
37
38
39
40
41
42
43
44

// 現在何文字目までを表示しているか
static int g_currentCursor = 0;// 何行目の文字を表示しているか
static int g_currentLineCursor = 0;// 白static int g_whiteColor ;// 黒static int g_blackColor ;// メッセージボックスの画像
static int g_messageBoxGraphicHandle ;64

第 6 章 サウンドノベル風メッセージ表示

45
46
47
48
49
50
51
52
53
54
55
56

// code が 日 本 語 で あ る か 判 定 す る// 戻り値 1: 日 本 語 0: 日 本 語 で は な い
int isJapaneseCharacter ( unsigned char code){if( (code >= 0x81 && code <= 0x9F) ||(code >= 0xE0 && code <= 0xFC) ) {return 1;}
return 0;
}

57
58
59
60
61
62
63
64
65

// m e s s a g e で 指 定 し た 文 章 を start の 位 置 か ら len 文 字 分 表 示 す る
void writeSubstring (char* message , int start , int len ,int posX , int posY , int color , int bufferLine ){int i;// 文字数
int maxLen = strlen ( message );66// s t a r t の 位 置 を 変 更 す る// s t a r t の 位 置 ま で に 日 本 語 が で て き て い た ら ， 1 を 足 し て い く
for( i = 0; i < start && message [i] != ’\0 ’; ) {if( isJapaneseCharacter ( message [i] ) ) {// 日本語の場合， 2 バ イ ト 分 す す め る
i += 2;// s t a r t に 1 バ イ ト 分 足 すstart ++;
}else {// 半角文字の場合， 1 バ イ ト 分 進 め る
i++;
}
}

67
68
69
70
71
72
73
74
75
76
77
78
79
80

// s t a r t の 位 置 が 表 示 し た い 最 大 文 字 数 よ り 大 き い 場 合if( start >= maxLen ) {return ;
}

81
82
83
84
85

// 指定した位置から l e n 文 字 分 表 示 す る
for( i = 0; i < len && message [ start + i ] != ’\0 ’; ) {if( isJapaneseCharacter ( message [ start + i ] ) ) {// 日本語の場合， 2 文 字 分 b u f f e r に セ ッ ト
g_messageBuffer [ bufferLine ][ i ] = message [ start + i ];g_messageBuffer [ bufferLine ][ i + 1 ] = message [ start + i + 1 ];// l e n は 日 本 語 な の で ， 1 バ イ ト 分 追 加 す る
len ++;//2 バ イ ト 分 進 め る
i += 2;}else {// 半角文字 1 文 字 を セ ッ ト
g_messageBuffer [ bufferLine ][ i ] = message [ start + i ];//1 バ イ ト 分 進 め る
i++;}}g_messageBuffer [ bufferLine ][i] = ’\0 ’;

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

// メッセージ描画
DrawString (posX , posY , g_messageBuffer [ bufferLine ], color );

105
106
107

}

108
109

65

6.9 サウンドノベル風メッセージ表示プログラム
110
111
112
113

// メッセージ描画
void drawMessage (){int i;

114// 文字が 1 文 字 も セ ッ ト さ れ て い な か っ た ら メ ッ セ ー ジ ボ ッ ク ス を 表 示 し な い
if( strnlen (g_message , MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE ) <= 0 ) {return ;
}

115
116
117
118
119

// メッセージボックス描画
DrawGraph ( MESSAGE_BOX_X_POS , MESSAGE_BOX_Y_POS , g_messageBoxGraphicHandle , FALSE );

120
121
122

if( g_message [ g_currentCursor ] != ’\0 ’ ) {g_currentCursor ++;
}

123
124
125
126

// MESSAGE_MAX_LENGTH ま で 文 字 を 描 画 し た ら 段 落 を 切 り 替 え る
if( g_currentCursor % MESSAGE_MAX_LENGTH == 0 ) {if( g_message [ g_currentCursor ] != ’\0 ’ ) {g_currentLineCursor ++;
}
}

127
128
129
130
131
132
133

// メッセージ描画部分
for( i = 0; i < MESSAGE_MAX_LINE ; i++ ) {if( i == g_currentLineCursor ) {// メッセージ風に表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH ,g_currentCursor - MESSAGE_MAX_LENGTH * i,
MESSAGE_BOX_X_POS + 15,
MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15,
g_blackColor , i );break;}else {// メッセージをそのまま表示
writeSubstring ( g_message , i * MESSAGE_MAX_LENGTH ,MESSAGE_MAX_LENGTH , MESSAGE_BOX_X_POS + 15,
MESSAGE_BOX_Y_POS + MESSAGE_FONT_SIZE * i + 15,
g_blackColor , i );
}
}

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

}

153
154
155
156
157
158
159
160
161
162
163

// 初期化処理
void initGame (){// 白
g_whiteColor = GetColor (255 ,255 ,255);// 黒g_blackColor = GetColor (0, 0, 0);// メッセージボックス
g_messageBoxGraphicHandle = LoadGraph ( MESSAGE_BOX_GRAPHIC_FILENAME );
}

164
165
166
167
168
169
170

// 描画したいメッセージをセット
void setMessage (const char* message ){// カーソルを初期化
g_currentCursor = 0;
g_currentLineCursor = 0;171// メッセージをコピー
strncpy ( g_message , message , MESSAGE_MAX_LENGTH * MESSAGE_MAX_LINE );

172
173
174

}

66

第 6 章 サウンドノベル風メッセージ表示

175
176
177
178
179
180
181
182
183
184
185
186

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

187// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

188
189
190

// 初期化処理
initGame ();

191
192
193

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

194
195
196

// 画面クリア
ClearDrawScreen ();

197
198
199

//F1 F 2 を 押 す と メ ッ セ ー ジ を セ ッ ト す る// F 3 を 押 す と 空 の 文 字 列 を セ ッ ト す る
if( CheckHitKey ( KEY_INPUT_F1 ) ) {setMessage (" はろーわーるど ");}else if( CheckHitKey ( KEY_INPUT_F2 ) ) {setMessage ("HELLO WORLD! あ い う え お か き く け こ さ し す せ そ た ち つ て と な "" にぬねのはひふへほまみむめもやゆよらりるれろわをん");}else if( CheckHitKey ( KEY_INPUT_F3 ) ) {setMessage ("");
}

200
201
202
203
204
205
206
207
208
209
210

// メッセージ描画
drawMessage ();

211
212
213

Sleep( 100 );ScreenFlip ();

214
215

}

216
217

DxLib_End ();return 0;

218
219
220

}

67

69

第 7 章 グラフィック管理
ゲームには欠かせないグラフィック管理部分を作っていきたいと思います．グラフィッ
ク管理とは，ゲーム画面上にどの画像を表示させるか等の情報を管理する部分です．完成
イメージは図 7.1 です．
ただ単に画像を画面に貼り付けているように見えますが，色々と工夫を凝らしています．
例えば，画像を画面に表示する都度，ハードディスクからイメージを読み込むと時間がかかってしまいます．そこで，あらかじめ画像をメモリ上に読み込んでおき，必要な時に表示させる方法をとっています．
また，画像を表示させる際には，フェードイン・フェードアウトを行うようにしてあります．

図 7.1: グラフィック管理7.1 メモリ上に画像を読み込む

7.1

メモリ上に画像を読み込む

まずは，メモリ上に画像をあらかじめロードしておき，その後画面に表示させてみま
しょう．なぜこのような方法を取るのでしょうか．DX ライブラリにはファイルからその都度画像をロードして表示する LoadGraphScreen 関数が存在しています．

int LoadGraphScreen( int x , int y , char *GraphName , int TransFlag );今回はこの関数は使わないわけですが，一応使い方を説明しておきます．引数 x, y には画像を表示したい座標を，GraphName には読み込む画像を，TransFlag は透過処理を行うかどうかを指定するものです．たとえば，フォルダ Pic 内の画像 kaeru1.png を座標 (50,50)に表示させるには以下のように引数を指定します．

LoadGraphScreen(50, 50, "./Pic/kaeru1.png", TRUE);
では，kaeru1.png を 10 箇所に表示させたい場合はどうでしょうか．LoadGraphScreen 関
数を 10 回呼び出す事になるのですが，これはハードディスクから同じ画像を 10 回呼び出すことになり，処理時間の遅れにつながります．ハードディスクからファイルを読み込むのは，メモリからの読み込みと比べかなり処理に時間がかかってしまうものです．
そこで，ハードディスクから画像をメモリ上に読み込んでおき，そこから必要なときに画面に表示させるといった方法が取られます．
さて，あらかじめ画像をメモリ上に読み込んでおくには，LoadGraph 関数を利用します．

int LoadGraph( char *FileName );FileName には読み込みたい画像のパスを指定します．この関数は画像の読み込みに成功した際，戻り値にグラフィックハンドルと呼ばれる画像識別番号が返ってきます（画像読
み込み失敗時には-1 が返ってきます）．この識別番号を DrawGraph 関数に渡してやること
で画像を表示することができます．さきほど LoadGraphScreen 関数を利用して画面に画像を表示させた方法を，LoadGraph 関数及び DrawGraph 関数を利用して書き直すと以下のようになります．
int kaeru = LoadGraph( "./Pic/kaeru1.png" );DrawGraph( 50, 50, kaeru, TRUE );DrawGraph 関数の第 3 引数にはグラフィックハンドル（画像識別番号）を渡します．その他は LoadGraphScreen 関数と全く同じです．
ではメモリに画像を読み込んでおき，画像に表示させるプログラムをお見せします．まずは一通りソースを眺めてみてください．ややステップ数が多いですが，頑張ってください．
リスト 7.1: ”graphic-01.cpp”
1# include "DxLib .h"

2
3

typedef struct GraphicNode_tag {70

第 7 章 グラフィック管理4
5
6

int id;// 画像の id 利 用 し て い な い 場 合 は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル} GraphicNode ;789// グラフィックの最大登録数# define GRAPHIC_MAX_NUM 3

10
11
12

// グラフィック管理
GraphicNode g_graphicManager [ GRAPHIC_MAX_NUM ];

13
14
15
16

// プロトタイプ宣言
void initGraphicNode ();int addGraphicNode (int id , const char* graphFilename );

17
18
19
20
21
22

// 初期化
void initGraphicNode (){// グラフィック管理初期化
memset ( & g_graphicManager , 0, sizeof ( GraphicNode ) * GRAPHIC_MAX_NUM );23// デバッグ用にコンソールを呼び出す
AllocConsole ();freopen (" CONOUT$ ", "w", stdout );freopen (" CONIN$ ", "r", stdin );

24
25
26
27
28

// 本当は使い終わったら FreeConsole () を 呼 ば な け れ ば な ら な い// ここでは省略

29
30
31

}

32
33
34
35
36
37
38
39
40
41
42
43
44

// グラフィックを読み込む// 戻り値 -1: 失 敗 0: 成 功
int addGraphicNode (int id , const char* graphFilename ){int i;// i d が 重 複 し て い な い か 確 認
for(i = 0; i < GRAPHIC_MAX_NUM ; i++) {if( id == g_graphicManager [i].id ) {printf (" i d が 重 複 し て い ま す (id %d)\n", id);
return -1;
}
}

45
46
47
48
49
50
51
52
53
54
55
56

// 利用していないノードを見つける
for(i = 0; i < GRAPHIC_MAX_NUM ; i++) {if( g_graphicManager [i].id == 0 ) {break;}}// グラフィックノードの空きがない
if( i == GRAPHIC_MAX_NUM ) {printf (" グラフィックノードの空きがありません (id %d)\n", id );return -1;
}

57
58
59
60
61

// i d を セ ッ ト
g_graphicManager [i]. id = id;// 画像読み込み
g_graphicManager [i]. graphicHandle = LoadGraph ( graphFilename );

62
63
64
65
66
67
68

// 読み込み失敗時
if( g_graphicManager [i]. graphicHandle == -1 ) {printf (" 画像読み込みに失敗しました (id %d)\n", id );// グラフィックノードを空き状態にする
g_graphicManager [i]. id = 0;g_graphicManager [i]. graphicHandle = 0;71

7.1 メモリ上に画像を読み込む
return -1;}
return 0;

69
70
71
72

}

73
74
75
76
77
78
79
80
81
82
83
84

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

85// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

86
87
88

// 初期化処理
initGraphicNode ();

89
90
91

// 画像追加
addGraphicNode (1, "./ pic/ kaeru1 .png");
addGraphicNode (1, "./ pic/ kaeru2 .png"); // 重複チェック
addGraphicNode (2, "./ pic/ kaeru2 .png");
addGraphicNode (3, "./ pic/ kaeru3 .png"); // 存在しない
addGraphicNode (3, "./ pic/ kaeru2 .png");
addGraphicNode (4, "./ pic/ kaeru2 .png"); // ノード不足

92
93
94
95
96
97
98
99

int i;

100
101// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

102
103
104

// 画面クリア
ClearDrawScreen ();

105
106
107

for(i = 0; i < GRAPHIC_MAX_NUM ; i++ ) {// 描画可能なグラフィックノードが存在するとき
if( g_graphicManager [i].id != 0 ) {// とりあえず適当な場所に描画
DrawGraph (30, i * 70, g_graphicManager [i]. graphicHandle , TRUE );}}ScreenFlip ();

108
109
110
111
112
113
114
115

}

116
117

DxLib_End ();return 0;

118
119
120

}

今回は GraphicNode という構造体を作りました．この構造体は，画像の ID とグラフィッ
クハンドル（画像識別番号）をセットにしたものです．また，グラフィックノードを配列(g graphicManager) とし，ID をキーとして，グラフィックハンドルを呼び出すような仕組みを作っていきます（図 7.2）．
このようなデータ構造は，マップと呼ばれます．マップはキーと値のペアであり，キーに対して値が一意に定まるような構造をしています．よって ID が重複することは決してありません．マップを実装するためには，さまざまなデータ構造が使われます．単純なマップは単方向リストで実装されていたり，ハッシュ関数を利用したものなども存在します．

72

第 7 章 グラフィック管理

図 7.2: GraphicNode を利用したマップ構造

詳しくはアルゴリズムの部をご覧ください．
さて，addGraphicNode 関数は GraphicNode の要素を登録するためのものです．id は重複して登録できない様に工夫しています．id に 0 がセットされている時はデータが登録されていないものと見なすようにしています．よって，新たにノードを追加する際は，g graphicManager
配列の先頭要素から id が 0 の場所を見つけ，その場所にデータを追加するようにしましょう．もし，g graphicManager の最後の要素まで検索しても利用可能な場所が確保できない
場合は，登録失敗とします (図 7.3）．

図 7.3: 画像データの登録73

7.2 画面に表示する画像の管理
今はあまり気にしなくてもよいですが，実はこのような方法では問題がたくさんありま
す．まず，g graphicManager を配列で実現している点です．これでは，配列の要素数分しか画像が登録できないことになります．また，利用していないノードを見つける方法も，先頭から要素を検索していく方法では効率が悪いです．このような時には，データ構造のリンクリストや，データ検索時にはハッシュ等を使うべきですが，いきなりそのような設計とするとプログラムが複雑になってしまうので，今回はそれらのアルゴリズムは利用していません．この章の最後には改良のポイントを示してあります．興味のある方は，ヒントを参照しながら，適切なアルゴリズムを利用してプログラムを作成してみてください．
さて，addGraphicNode 関数では，printf 関数をデバッグメッセージを表示するために利用しています．しかし，DX ライブラリは，標準ではコンソール画面を表示させることができません．そこで，initGraphicNode 関数では，コンソールが利用できるよう以下のようなコードを書いています．

AllocConsole();freopen("CONOUT$", "w", stdout);
freopen("CONIN$", "r", stdin);
このコードの意味は理解しなくてかまいません．とにかく，このようにすればコンソールを呼び出すことができます．
では，main 関数内で正しい動作をするか確認していきましょう．画像をただメモリにロードしただけでは，正しく動作しているのかが見えづらいです．そこで，今回はメイン
ループ内で読み込んだ画像を適当な場所に表示させています．
実行結果は図 7.4 及び以下のようになります．
¶
実行結果

³

id が重複しています (id 1)
画像読み込みに失敗しました (id 3)
グラフィックノードの空きがありません (id 4)
µ

7.2

´

画面に表示する画像の管理

前節で画像をメモリ上に読み込む仕組みを作ることができました．今回は，読み込んだ画像を画面に表示させる仕組みを作っていきましょう．
画面に画像を表示するためには，画像を表示する位置情報が必要です．そこで，位置情報とグラフィックハンドルを持った構造体，VisibleGraphicNode を作っていきましょう．また，これらのノードを管理する配列，g visibleGraphic も作成します．
では，画面に表示する画像を管理するプログラムをお見せします．
リスト 7.2: ”graphic-02.cpp”
1# include "DxLib .h"23// グラフィックを管理

74

第 7 章 グラフィック管理

図 7.4: メモリ上の画像を表示するプログラム75

7.2 画面に表示する画像の管理
45
67

typedef struct GraphicNode_tag {int id;// 画像の id 利 用 し て い な い 場 合 は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル} GraphicNode ;

8
9
10
11
12
13
14
15

// 画面に表示するグラフィックを管理
typedef struct VisibleGraphicNode_tag {int graphicId ; // 画像の id G r a p h i c N o d e の i d と は 別 物 な の で 注 意// 利用していない場合は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル
int x,y; // 表示する座標} VisibleGraphicNode ;

16
17
18
19
20
21

// グラフィックの最大登録数# define GRAPHIC_MAX_NUM 3// 画面に表示できる最大の画像数# define VISIBLE_GRAPHIC_MAX_NUM 10

22
23
24

// グラフィック管理
GraphicNode g_graphicManager [ GRAPHIC_MAX_NUM ];

25
26
27

// 表示するグラフィックの管理
VisibleGraphicNode g_visibleGraphic [ VISIBLE_GRAPHIC_MAX_NUM ];

28
29
30
31
32

// プロトタイプ宣言
void initGraphicNode ();int addGraphicNode (int id , const char* graphFilename );int getGraphicHandle (int id);

33
34
35

int addVisibleGraphic (int id , int graphicId , int posX , int posY );void drawVisibleGraphic ();

36
37
38
39
40
41
42
43

// 初期化
void initGraphicNode (){// グラフィック管理初期化
memset ( & g_graphicManager , 0, sizeof ( GraphicNode ) * GRAPHIC_MAX_NUM );// 表示するグラフィックの管理初期化
memset ( & g_visibleGraphic , 0, sizeof ( VisibleGraphicNode ) * VISIBLE_GRAPHIC_MAX_NUM );44// デバッグ用にコンソールを呼び出す
AllocConsole ();freopen (" CONOUT$ ", "w", stdout );freopen (" CONIN$ ", "r", stdin );

45
46
47
48
49

// 本当は使い終わったら FreeConsole () を 呼 ば な け れ ば な ら な い// ここでは省略

50
51
52

}

53
54
55
56
57
58
59

// グラフィックを読み込む// 戻り値 -1: 失 敗 0: 成 功
int addGraphicNode (int id , const char* graphFilename ){// 省略（前回と同じ）
}

60
61
62
63
64
65
66
67
68

// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得 す る
int getGraphicHandle (int id){
int i = 0;
for(i = 0; i < GRAPHIC_MAX_NUM ; i++ ) {if( id == g_graphicManager [i].id ) {return g_graphicManager [i]. graphicHandle ;}76

第 7 章 グラフィック管理}
return -1;

69
70
71

}

72
73
74
75
76
77
78
79
80
81
82

// 画面に画像を表示する// i d に は G r a p h i c N o d e の i d を 指 定 ， g r a p h i c I d は い ま か ら 描 画 す る 画 像 に i d を 付 け る//posX , p o s Y は 画 像 を 表 示 す る 位 置// 戻り値 -1: 失 敗 0: 成 功
int addVisibleGraphic (int id , int graphicId , int posX , int posY){
int i;// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得
int handle = getGraphicHandle ( id );83// g r a p h i c H a n d l e の 取 得 失 敗
if( handle == -1 ) {printf ("ID: %d の 画 像 は 登 録 さ れ て い ま せ ん \ n", id );return -1;
}

84
85
86
87
88
89

// g r a p h i c I d が 重 複 し て い な い か 確 認
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( graphicId == g_visibleGraphic [i]. graphicId ) {printf ("graph i d が 重 複 し て い ま す (id %d)\n", graphicId );return -1;
}
}

90
91
92
93
94
95
96
97

// 利用していないノードを見つける
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( g_visibleGraphic [i]. graphicId == 0 ) {break;
}
}

98
99
100
101
102
103
104

// ノードの空きがない
if( i == VISIBLE_GRAPHIC_MAX_NUM ) {printf (" 画面にこれ以上画像を表示できません (id %d)\n", graphicId );return -1;
}

105
106
107
108
109
110

// 情報登録// グラフィックハンドル登録
g_visibleGraphic [i]. graphicHandle = handle ;// g r a p h i c I d を 登 録
g_visibleGraphic [i]. graphicId = graphicId ;// 座標を登録
g_visibleGraphic [i].x = posX;
g_visibleGraphic [i].y = posY;

111
112
113
114
115
116
117
118
119

return 0;

120
121

}

122
123
124
125
126
127
128
129
130
131
132
133

// 画像描画
void drawVisibleGraphic (){int i;
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++ ) {// g r a p h i c I d が 0 で な け れ ば （ 画 像 が 存 在 す れ ば )if( g_visibleGraphic [i]. graphicId != 0 ) {// 指定した座標に画像描画
DrawGraph ( g_visibleGraphic [i].x, g_visibleGraphic [i].y,g_visibleGraphic [i]. graphicHandle , TRUE );}77

7.2 画面に表示する画像の管理
}

134
135

}

136
137
138
139
140
141
142
143
144
145
146
147
148

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

149// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

150
151
152

// 初期化処理
initGraphicNode ();

153
154
155

// 画像追加
addGraphicNode (1, "./ pic/ kaeru1 .png");
addGraphicNode (2, "./ pic/ kaeru2 .png");
addGraphicNode (3, "./ pic/ kaeru2 .png");

156
157
158
159
160

// 表示する画像追加
addVisibleGraphic (1, 1, 10, 10);
addVisibleGraphic (2, 2, 50, 10);
addVisibleGraphic (3, 3, 50, 50);// 同じ g r a p h i c I d を 2 回 登 録 ( エ ラ ー )addVisibleGraphic (1, 3, 10, 10);// 存在しない g r a p h i c N o d e の i d を 指 定 （ エ ラ ー ）addVisibleGraphic (4, 4, 10, 10);// 画像をたくさん追加
addVisibleGraphic (1, 4, 100, 50);
addVisibleGraphic (1, 5, 200, 50);
addVisibleGraphic (1, 6, 300, 50);
addVisibleGraphic (1, 7, 100, 200);
addVisibleGraphic (1, 8, 200, 200);
addVisibleGraphic (1, 9, 300, 200);
addVisibleGraphic (2, 10 ,350 , 300);// これ以上画像を追加できない
addVisibleGraphic (2, 11, 300, 300);

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

180
181
182

// 画面クリア
ClearDrawScreen ();

183
184
185

// 画像描画
drawVisibleGraphic ();

186
187
188

ScreenFlip ();

189

}

190
191

DxLib_End ();return 0;

192
193
194

}

画像の位置情報を持った VisibleGraphicNode の構造も，前回の GraphicNode と同じよ
うにマップ構造となっています．ここで，VisibleGraphicNode の持っている graphicId と，

78

第 7 章 グラフィック管理

GraphicNode の id とは別物なので注意してください．graphicId は画像の位置情報及びグ
ラフィックハンドルのキーとなるものです．
addVisibleGraphic 関数は画面に表示する画像を登録するためのものです．登録された画
像は g visibleGraphic 配列で管理されています．第 1 引数の id には GraphicNode の id を，
第 2 引数には VisibleGraphicNode のキーとなる graphicId を，第 3，第 4 引数には画像を表示する場所を指定します．
drawVisibleGraphic 関数は，g visibleGraphic 配列に保存されている画像を画面に表示するためのものです．
main 関数では，ただしくプログラムが動作するか確認のテストを行っています．このプ
ログラムの実行結果は図 7.5 及び以下のようになります．
¶³
実行結果
graph id が重複しています (id 3)
ID: 4 の画像は登録されていません
画面にこれ以上画像を表示できません (id 11)µ´

図 7.5: 画面への画像の表示

797.3 画面に表示された画像の削除

7.3

画面に表示された画像の削除

今回は，前節で addVisibleGraphic 関数を利用して画面上に描画した画像を削除する関数を作ってみたいと思います．特に難しい処理はありません．単に graphicId を 0 に書き換
えれば画像は消えた事になるのです．
では，画像削除機能を書き加えたプログラムをお見せします．
リスト 7.3: ”graphic-03.cpp”
1# include "DxLib .h"

2
3
4
5
6
7

// グラフィックを管理
typedef struct GraphicNode_tag {int id;// 画像の id 利 用 し て い な い 場 合 は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル} GraphicNode ;

8
9
10
11
12
13
14
15

// 画面に表示するグラフィックを管理
typedef struct VisibleGraphicNode_tag {int graphicId ; // 画像の id G r a p h i c N o d e の i d と は 別 物 な の で 注 意// 利用していない場合は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル
int x,y; // 表示する座標} VisibleGraphicNode ;

16
17
18
19
20
21

// グラフィックの最大登録数# define GRAPHIC_MAX_NUM 3// 画面に表示できる最大の画像数# define VISIBLE_GRAPHIC_MAX_NUM 10

22
23
24

// グラフィック管理
GraphicNode g_graphicManager [ GRAPHIC_MAX_NUM ];

25
26
27

// 表示するグラフィックの管理
VisibleGraphicNode g_visibleGraphic [ VISIBLE_GRAPHIC_MAX_NUM ];

28
29
30
31
32

// プロトタイプ宣言
void initGraphicNode ();int addGraphicNode (int id , const char* graphFilename );int getGraphicHandle (int id);

33
34
35
36

int addVisibleGraphic (int id , int graphicId , int posX , int posY );void drawVisibleGraphic ();int removeVisibleGraphic (int graphicId );

37
38
39
40
41
42

// 初期化
void initGraphicNode (){// 省略（前回と同じ）
}

43
44
45
46
47
48
49

// グラフィックを読み込む// 戻り値 -1: 失 敗 0: 成 功
int addGraphicNode (int id , const char* graphFilename ){// 省略（前回と同じ）
}

50
51
52
53
54

// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得 す る
int getGraphicHandle (int id){// 省略（前回と同じ）

80

第 7 章 グラフィック管理

55

}

56
57
58
59
60
61
62
63
64
65

// 画面に画像を表示する// i d に は G r a p h i c N o d e の i d を 指 定 ， g r a p h i c I d は い ま か ら 描 画 す る 画 像 に i d を 付 け る//posX , p o s Y は 画 像 を 表 示 す る 位 置// 戻り値 -1: 失 敗 0: 成 功
int addVisibleGraphic (int id , int graphicId , int posX , int posY){// 省略（前回と同じ）
}

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

// 指定した g r a p h i c I d の 画 像 を 削 除 す る// 戻り値 -1: 失 敗 0: 成 功
int removeVisibleGraphic (int graphicId ){int i;
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( graphicId == g_visibleGraphic [i]. graphicId ) {// 指定したグラフィックが見つかった// データを削除
g_visibleGraphic [i]. graphicHandle = 0;
g_visibleGraphic [i]. graphicId = 0;
g_visibleGraphic [i].x = 0;
g_visibleGraphic [i].y = 0;
return 0;}}
return -1;
}

85
86
87
88
89
90
91

// 画像描画
void drawVisibleGraphic (){// 省略（前回と同じ）
}

92
93
94
95
96
97
98
99
100
101
102
103
104

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

105
106
107

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

108
109
110

// 初期化処理
initGraphicNode ();

111
112
113
114
115

// 画像追加
addGraphicNode (1, "./ pic/ kaeru1 .png");
addGraphicNode (2, "./ pic/ kaeru2 .png");
addGraphicNode (3, "./ pic/ kaeru2 .png");

116
117
118
119

// 表示する画像追加
addVisibleGraphic (1, 1, 10, 10);
addVisibleGraphic (2, 2, 50, 10);81

7.4 画像のフェードイン・フェードアウト
addVisibleGraphic (3, 3, 50, 50);
addVisibleGraphic (1, 4, 100, 50);
addVisibleGraphic (1, 5, 200, 50);
addVisibleGraphic (1, 6, 300, 50);
addVisibleGraphic (1, 7, 100, 200);
addVisibleGraphic (1, 8, 200, 200);
addVisibleGraphic (1, 9, 300, 200);
addVisibleGraphic (2, 10 ,350 , 300);// これ以上画像を追加できない
addVisibleGraphic (2, 11, 300, 300);// 画像を削除
removeVisibleGraphic (4);removeVisibleGraphic (5);removeVisibleGraphic (6);removeVisibleGraphic (7);removeVisibleGraphic (8);removeVisibleGraphic (9);removeVisibleGraphic (10);// こんどは画像を登録できる
addVisibleGraphic (1, 4, 300, 300);

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

142
143
144

// 画面クリア
ClearDrawScreen ();

145
146
147

// 画像描画
drawVisibleGraphic ();

148
149
150

ScreenFlip ();

151

}

152
153

DxLib_End ();return 0;

154
155
156

}

今回追加した部分は removeVisibleGraphic 関数だけです．このプログラムの実行結果は
図 7.6 及び以下のようになりました．
¶
実行結果

³

画面にこれ以上画像を表示できません (id 11)
µ

7.4

´

画像のフェードイン・フェードアウト

画像を画面に表示する際，だんだん明るくしていったり，逆に画像を画面から削除する
ときはだんだん暗くしていったりといったフェードイン，フェードアウト効果を付けてみましょう．画像の明るさ（輝度）を変えるには SetDrawBright 関数を利用します．

int SetDrawBright( int RedBright , int GreenBright , int BlueBright );RedBright, GreenBright, BlueBright はそれぞれ赤，緑，青の輝度を表しています．指定できる値は，0〜255 の範囲です．0 は真っ暗，255 は最高輝度です．
さて，フェードイン・フェードアウトを行うプログラムをお見せします．特に難しいことはありません．時間が立つにつれて，ただ輝度を少しずつ変えていけばいいのです．
82

第 7 章 グラフィック管理

図 7.6: 画像の削除機能を備えたプログラム

837.4 画像のフェードイン・フェードアウト
リスト 7.4: ”graphic-04-01.cpp”
1# include "DxLib .h"

2
3
4
5
6
7
8

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int i;
int graphicHandle ;9// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600, 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

10
11
12
13
14
15
16
17
18

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );

19
20
21

// 画像読み込み
graphicHandle = LoadGraph ("./ pic/man.png");

22
23
24

// フェードイン
for(i = 0; i < 255; i++ ) {// 輝度をだんだんあげていく
SetDrawBright (i, i, i);// 画像描画
DrawGraph (50 ,100 , graphicHandle , TRUE );ScreenFlip ();
}

25
26
27
28
29
30
31
32
33

// フェードアウト
for(i = 255; i >= 0; i--) {// 輝度をだんだんさげていく
SetDrawBright (i, i, i);// 画像描画
DrawGraph (50 ,100 , graphicHandle , TRUE );ScreenFlip ();
}

34
35
36
37
38
39
40
41
42

WaitKey ();43
44

DxLib_End ();return 0;

45
46
47

}

フェードイン・フェードアウトはできました．しかし，背景画像の上に画像をフェード
イン・フェードアウトさせた場合，どのように表示されるでしょうか．是非ご自身で試してみてください．なんだか，画像が黒くなっていくだけで違和感があります（図 7.7）．
さて，背景画像の上にだんだん画像を表示させていく，またはだんだん画像を消していく為には，アルファブレンドと呼ばれる手法を使います．アルファブレンドを用いると，画像が透けている感じを出すことができます (図 7.8）．
アルファブレンドを行うには SetDrawBlendMode 関数を利用します．

int SetDrawBlendMode( int BlendMode , int Pal );84

第 7 章 グラフィック管理

図 7.7: 違和感のある画像のフェードアウト

図 7.8: アルファブレンドを利用した画像のフェードアウト85

7.4 画像のフェードイン・フェードアウト
BlendMode には描画するブレンドモードを指定することができます．アルファブレン
ドを利用するには，DX BLENDMODE ALPHA を指定します．Pal にはブレンドモードのパラメータ（範囲 0〜255) を指定します．ブレンドモードを元に戻すには，BlendMode に
DX BLENDMODE NOBLEND を渡します．
では，アルファブレンドを利用したプログラムをお見せします．
リスト 7.5: ”graphic-04-02.cpp”
1# include "DxLib .h"

2
3
4
5
6
7

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){int i;
int graphicHandle , backHandle ;

8
9
10
11
12
13
14
15
16

// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600, 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

17
18
19
20
21
22
23

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );// 画像読み込み
graphicHandle = LoadGraph ("./ pic/man.png");// 背景を描画
backHandle = LoadGraph ("./ pic/back.png");

24
25
26
27
28

// フェードイン
for(i = 0; i < 255; i++ ) {// 画面削除
ClearDrawScreen ();29// 背景表示DrawGraph ( 0, 0, backHandle , FALSE );// 輝度をだんだんあげていく
SetDrawBright (i, i, i);// アルファブレンドを行う
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , i );// 画像描画
DrawGraph (50 ,100 , graphicHandle , TRUE );

30
31
32
33
34
35
36
37
38

// 輝度，アルファブレンドの情報を元に戻す
SetDrawBright (255 , 255, 255);
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

39
40
41
42

ScreenFlip ();

43
44

}

45
46
47
48
49

// フェードアウト
for(i = 255; i >= 0; i--) {// 画面削除
ClearDrawScreen ();

50
51
52
53
54
55

// 背景表示DrawGraph ( 0, 0, backHandle , FALSE );// 輝度をだんだんさげていく
SetDrawBright (i, i, i);// アルファブレンドを行う

86

第 7 章 グラフィック管理
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , i );// 画像描画
DrawGraph (50 ,100 , graphicHandle , TRUE );

56
57
58
59

// 輝度，アルファブレンドの情報を元に戻す
SetDrawBright (255 , 255, 255);
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

60
61
62
63

ScreenFlip ();

64

}

65
66

WaitKey ();67
68

DxLib_End ();return 0;

69
70
71

}

7.5

輝度情報を持った画像の管理

画像を画面に表示する際，フェードインを行うようにしてみましょう．この機能を実現するために，VisibleGraphicNode 構造体に新たに輝度情報とモードを追加してみます．モードはこれから画像をフェードインするかフェードアウトするかそうでないかの情報を持っています．画像を追加した際はモードをフェードインとしておきます．フェードインモー
ド時は，画面に画像を描画する際に輝度情報を少しずつ上げていきます．そして，輝度が最大値となったところでフェードインモードを解除します．
では，フェードイン機能を追加した画像管理プログラムをお見せします．
リスト 7.6: ”graphic-05.cpp”
1# include "DxLib.h"

2
3
4
5
6
7

// グラフィックを管理
typedef struct GraphicNode_tag {int id;// 画像の id 利 用 し て い な い 場 合 は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル} GraphicNode ;

8
9
10
11
12
13
14
15
16
17

// 画面に表示するグラフィックを管理
typedef struct VisibleGraphicNode_tag {int graphicId ; // 画像の id G r a p h i c N o d e の i d と は 別 物 な の で 注 意// 利用していない場合は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル
int x,y; // 表示する座標
int bright ; // 輝度
int mode; // 画像描画モード 0: 輝 度 変 化 無 し 1: フ ェ ー ド イ ン 2: フ ェ ー ド ア ウ ト} VisibleGraphicNode ;

18
19
20
21
22
23
24
25
26
27
28
29

// グラフィックの最大登録数# define GRAPHIC_MAX_NUM 3// 画面に表示できる最大の画像数# define VISIBLE_GRAPHIC_MAX_NUM 10// フェードイン・フェードアウトのスピード# define GRAPHIC_FADEIN_FADEOUT_SPEED 10// 輝度の最大値# define GRAPHIC_MAX_BRIGHT 255// 画像描画モード# define GRAPHIC_MODE_NONE 087

7.5 輝度情報を持った画像の管理
30
31# define GRAPHIC_MODE_FADEIN 1# define GRAPHIC_MODE_FADEOUT 2

32
33
34

// グラフィック管理
GraphicNode g_graphicManager [ GRAPHIC_MAX_NUM ];

35
36
37

// 表示するグラフィックの管理
VisibleGraphicNode g_visibleGraphic [ VISIBLE_GRAPHIC_MAX_NUM ];

38
39
40
41
42

// プロトタイプ宣言
void initGraphicNode ();int addGraphicNode (int id , const char* graphFilename );int getGraphicHandle (int id);

43
44
45
46

int addVisibleGraphic (int id , int graphicId , int posX , int posY );void drawVisibleGraphic ();int removeVisibleGraphic (int graphicId );

47
48
49
50
51
52

// 初期化
void initGraphicNode (){// 省略（前回と同じ）
}

53
54
55
56
57
58
59

// グラフィックを読み込む// 戻り値 -1: 失 敗 0: 成 功
int addGraphicNode (int id , const char* graphFilename ){// 省略（前回と同じ）
}

60
61
62
63
64
65

// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得 す る
int getGraphicHandle (int id){// 省略（前回と同じ）
}

66
67
68
69
70
71
72
73
74
75
76

// 画面に画像を表示する// i d に は G r a p h i c N o d e の i d を 指 定 ， g r a p h i c I d は い ま か ら 描 画 す る 画 像 に i d を 付 け る//posX , p o s Y は 画 像 を 表 示 す る 位 置// 戻り値 -1: 失 敗 0: 成 功
int addVisibleGraphic (int id , int graphicId , int posX , int posY){
int i;// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得
int handle = getGraphicHandle ( id );

77
78
79
80
81
82

// g r a p h i c H a n d l e の 取 得 失 敗
if( handle == -1 ) {printf ("ID: %d の 画 像 は 登 録 さ れ て い ま せ ん \ n", id );return -1;
}

83
84
85
86
87
88
89
90

// g r a p h i c I d が 重 複 し て い な い か 確 認
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( graphicId == g_visibleGraphic [i]. graphicId ) {printf ("graph i d が 重 複 し て い ま す (id %d)\n", graphicId );return -1;
}
}

91
92
93
94

// 利用していないノードを見つける
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( g_visibleGraphic [i]. graphicId == 0 ) {88

第 7 章 グラフィック管理
break;

95

}

96

}

97
98

// ノードの空きがない
if( i == VISIBLE_GRAPHIC_MAX_NUM ) {printf (" 画面にこれ以上画像を表示できません (id %d)\n", graphicId );return -1;
}

99
100
101
102
103
104

// 情報登録// グラフィックハンドル登録
g_visibleGraphic [i]. graphicHandle = handle ;// g r a p h i c I d を 登 録
g_visibleGraphic [i]. graphicId = graphicId ;// 座標を登録
g_visibleGraphic [i].x = posX;
g_visibleGraphic [i].y = posY;// 輝度情報登録
g_visibleGraphic [i]. bright = 0;// モードg_visibleGraphic [i]. mode = GRAPHIC_MODE_FADEIN ;

105
106
107
108
109
110
111
112
113
114
115
116
117

return 0;

118
119

}

120
121
122
123
124
125
126

// 指定した g r a p h i c I d の 画 像 を 削 除 す る// 戻り値 -1: 失 敗 0: 成 功
int removeVisibleGraphic (int graphicId ){// 省略（前回と同じ）
}

127
128
129
130
131
132
133
134
135

// 画像描画
void drawVisibleGraphic (){int i;
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++ ) {// g r a p h i c I d が 0 で な け れ ば （ 画 像 が 存 在 す れ ば )if( g_visibleGraphic [i]. graphicId != 0 ) {

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

// フェードインを行うとき
if( g_visibleGraphic [i]. mode == GRAPHIC_MODE_FADEIN ) {// 輝度を上げる
g_visibleGraphic [i]. bright += GRAPHIC_FADEIN_FADEOUT_SPEED ;if( g_visibleGraphic [i]. bright > GRAPHIC_MAX_BRIGHT ) {// 輝度が最大値に達した時
g_visibleGraphic [i]. bright = GRAPHIC_MAX_BRIGHT ;// フェードイン解除
g_visibleGraphic [i]. mode = GRAPHIC_MODE_NONE ;}}// 輝度セット
SetDrawBright ( g_visibleGraphic [i]. bright ,g_visibleGraphic [i]. bright , g_visibleGraphic [i]. bright );// アルファブレンドを行う
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , g_visibleGraphic [i]. bright ) ;// 指定した座標に画像描画
DrawGraph ( g_visibleGraphic [i].x, g_visibleGraphic [i].y,g_visibleGraphic [i]. graphicHandle , TRUE );}}// 輝度情報をデフォルトに戻しておく
SetDrawBright ( GRAPHIC_MAX_BRIGHT , GRAPHIC_MAX_BRIGHT , GRAPHIC_MAX_BRIGHT );89

7.5 輝度情報を持った画像の管理// ブレンドモードを元にもどしておく
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , GRAPHIC_MAX_BRIGHT );

160
161
162

}

163
164
165
166
167
168
169
170
171
172
173
174
175

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

176
177
178

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

179
180
181

// 初期化処理
initGraphicNode ();

182
183
184
185
186

// 画像追加
addGraphicNode (1, "./ pic/ kaeru1 .png");
addGraphicNode (2, "./ pic/ kaeru2 .png");
addGraphicNode (3, "./ pic/ kaeru2 .png");

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210

// 表示する画像追加
addVisibleGraphic (1, 1, 10, 10);
addVisibleGraphic (2, 2, 50, 10);
addVisibleGraphic (3, 3, 50, 50);
addVisibleGraphic (1, 4, 100, 50);
addVisibleGraphic (1, 5, 200, 50);
addVisibleGraphic (1, 6, 300, 50);
addVisibleGraphic (1, 7, 100, 200);
addVisibleGraphic (1, 8, 200, 200);
addVisibleGraphic (1, 9, 300, 200);
addVisibleGraphic (2, 10 ,350 , 300);// これ以上画像を追加できない
addVisibleGraphic (2, 11, 300, 300);// 画像を削除
removeVisibleGraphic (4);removeVisibleGraphic (5);removeVisibleGraphic (6);removeVisibleGraphic (7);removeVisibleGraphic (8);removeVisibleGraphic (9);removeVisibleGraphic (10);// こんどは画像を登録できる
addVisibleGraphic (1, 4, 300, 300);

211
212
213
214

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

215
216
217

// 画面クリア
ClearDrawScreen ();

218
219
220

// 画像描画
drawVisibleGraphic ();

221
222

Sleep (50);

223
224

ScreenFlip ();90

第 7 章 グラフィック管理
}

225
226

DxLib_End ();return 0;

227
228
229

}

7.6

グラフィック管理プログラム

ようやくグラフィック管理部分の完成です．今回は，前回のプログラムにさらに画像を削除する際，フェードアウトする機能を追加してみましょう．特に難しいことはありません．ただ，画像を削除したくなったら，モードをフェードアウトとし，輝度が 0 となった
時，画像を削除すればよいのです．
では，完成したプログラムをお見せします．今回はコードを省略せず全て書いています．
画像が表示された後，F1 キーを押すことで画像を削除させることができます．
リスト 7.7: ”graphic-06.cpp”
1# include "DxLib.h"

2
3
4
5
6
7

// グラフィックを管理
typedef struct GraphicNode_tag {int id;// 画像の id 利 用 し て い な い 場 合 は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル} GraphicNode ;

8
9
10
11
12
13
14
15
16
17

// 画面に表示するグラフィックを管理
typedef struct VisibleGraphicNode_tag {int graphicId ; // 画像の id G r a p h i c N o d e の i d と は 別 物 な の で 注 意// 利用していない場合は 0 が セ ッ ト さ れ て い る
int graphicHandle ; // 画像のグラフィックハンドル
int x,y; // 表示する座標
int bright ; // 輝度
int mode; // 画像描画モード 0: 輝 度 変 化 無 し 1: フ ェ ー ド イ ン 2: フ ェ ー ド ア ウ ト} VisibleGraphicNode ;

18
19
20
21
22
23
24
25
26
27
28
29
30
31

// グラフィックの最大登録数# define GRAPHIC_MAX_NUM 3// 画面に表示できる最大の画像数# define VISIBLE_GRAPHIC_MAX_NUM 10// フェードイン・フェードアウトのスピード# define GRAPHIC_FADEIN_FADEOUT_SPEED 10// 輝度の最大値# define GRAPHIC_MAX_BRIGHT 255// 画像描画モード# define GRAPHIC_MODE_NONE 0# define GRAPHIC_MODE_FADEIN 1# define GRAPHIC_MODE_FADEOUT 2

32
33
34

// グラフィック管理
GraphicNode g_graphicManager [ GRAPHIC_MAX_NUM ];

35
36
37

// 表示するグラフィックの管理
VisibleGraphicNode g_visibleGraphic [ VISIBLE_GRAPHIC_MAX_NUM ];

38
39
40
41

// プロトタイプ宣言
void initGraphicNode ();int addGraphicNode (int id , const char* graphFilename );91

7.6 グラフィック管理プログラム
42

int getGraphicHandle (int id);

43
44
45
46

int addVisibleGraphic (int id , int graphicId , int posX , int posY );void drawVisibleGraphic ();int removeVisibleGraphic (int graphicId );

47
48
49
50
51
52
53
54

// 初期化
void initGraphicNode (){// グラフィック管理初期化
memset ( & g_graphicManager , 0, sizeof ( GraphicNode ) * GRAPHIC_MAX_NUM );// 表示するグラフィックの管理初期化
memset ( & g_visibleGraphic , 0, sizeof ( VisibleGraphicNode ) * VISIBLE_GRAPHIC_MAX_NUM );55// デバッグ用にコンソールを呼び出す
AllocConsole ();freopen (" CONOUT$ ", "w", stdout );freopen (" CONIN$ ", "r", stdin );

56
57
58
59
60

// 本当は使い終わったら FreeConsole () を 呼 ば な け れ ば な ら な い// ここでは省略

61
62
63

}

64
65
66
67
68
69
70
71
72
73
74
75
76

// グラフィックを読み込む// 戻り値 -1: 失 敗 0: 成 功
int addGraphicNode (int id , const char* graphFilename ){int i;// i d が 重 複 し て い な い か 確 認
for(i = 0; i < GRAPHIC_MAX_NUM ; i++) {if( id == g_graphicManager [i].id ) {printf (" i d が 重 複 し て い ま す (id %d)\n", id );return -1;}}

77// 利用していないノードを見つける
for(i = 0; i < GRAPHIC_MAX_NUM ; i++) {if( g_graphicManager [i].id == 0 ) {break;}}// グラフィックノードの空きがない
if( i == GRAPHIC_MAX_NUM ) {printf (" グラフィックノードの空きがありません (id %d)\n", id );return -1;
}

78
79
80
81
82
83
84
85
86
87
88
89

// 画像読み込み
g_graphicManager [i]. graphicHandle = LoadGraph ( graphFilename );

90
91
92

// 読み込み失敗時
if( g_graphicManager [i]. graphicHandle == -1 ) {printf (" 画像読み込みに失敗しました (id %d)\n", id );g_graphicManager [i]. graphicHandle = 0;
return -1;
}

93
94
95
96
97
98
99

// i d を セ ッ ト
g_graphicManager [i]. id = id;
return 0;

100
101
102
103

}

104
105
106

// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得 す る
int getGraphicHandle (int id)92

第 7 章 グラフィック管理107{
int i = 0;
for(i = 0; i < GRAPHIC_MAX_NUM ; i++ ) {if( id == g_graphicManager [i].id ) {return g_graphicManager [i]. graphicHandle ;}}return -1;

108
109
110
111
112
113
114
115

}

116
117
118
119
120
121
122
123
124
125
126

// 画面に画像を表示する// i d に は G r a p h i c N o d e の i d を 指 定 ， g r a p h i c I d は い ま か ら 描 画 す る 画 像 に i d を 付 け る//posX , p o s Y は 画 像 を 表 示 す る 位 置// 戻り値 -1: 失 敗 0: 成 功
int addVisibleGraphic (int id , int graphicId , int posX , int posY){
int i;// i d か ら グ ラ フ ィ ッ ク ハ ン ド ル を 取 得
int handle = getGraphicHandle ( id );127// g r a p h i c H a n d l e の 取 得 失 敗
if( handle == -1 ) {printf ("ID: %d の 画 像 は 登 録 さ れ て い ま せ ん \ n", id );return -1;
}

128
129
130
131
132
133

// g r a p h i c I d が 重 複 し て い な い か 確 認
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( graphicId == g_visibleGraphic [i]. graphicId ) {printf ("graph i d が 重 複 し て い ま す (id %d)\n", graphicId );return -1;
}
}

134
135
136
137
138
139
140
141

// 利用していないノードを見つける
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( g_visibleGraphic [i]. graphicId == 0 ) {break;
}
}

142
143
144
145
146
147
148

// ノードの空きがない
if( i == VISIBLE_GRAPHIC_MAX_NUM ) {printf (" 画面にこれ以上画像を表示できません (id %d)\n", graphicId );return -1;
}

149
150
151
152
153
154

// 情報登録// グラフィックハンドル登録
g_visibleGraphic [i]. graphicHandle = handle ;// g r a p h i c I d を 登 録
g_visibleGraphic [i]. graphicId = graphicId ;// 座標を登録
g_visibleGraphic [i].x = posX;
g_visibleGraphic [i].y = posY;// 輝度情報登録
g_visibleGraphic [i]. bright = 0;// モードg_visibleGraphic [i]. mode = GRAPHIC_MODE_FADEIN ;

155
156
157
158
159
160
161
162
163
164
165
166
167

return 0;

168
169

}

170
171

// 指定した g r a p h i c I d の 画 像 を 削 除 す る93

7.6 グラフィック管理プログラム
172
173
174
175
176
177
178
179
180
181
182
183
184
185

// 戻り値 -1: 失 敗 0: 成 功
int removeVisibleGraphic (int graphicId ){int i;
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++) {if( graphicId == g_visibleGraphic [i]. graphicId ) {// 指定したグラフィックが見つかった// モードをフェードアウトとする
g_visibleGraphic [i]. mode = GRAPHIC_MODE_FADEOUT ;return 0;}}
return -1;
}

186
187
188
189
190
191
192
193
194

// 画像描画
void drawVisibleGraphic (){int i;
for(i = 0; i < VISIBLE_GRAPHIC_MAX_NUM ; i++ ) {// g r a p h i c I d が 0 で な け れ ば （ 画 像 が 存 在 す れ ば )if( g_visibleGraphic [i]. graphicId != 0 ) {

195
196
197
198
199
200
201
202
203
204
205
206

// フェードインを行うとき
if( g_visibleGraphic [i]. mode == GRAPHIC_MODE_FADEIN ) {// 輝度を上げる
g_visibleGraphic [i]. bright += GRAPHIC_FADEIN_FADEOUT_SPEED ;if( g_visibleGraphic [i]. bright > GRAPHIC_MAX_BRIGHT ) {// 輝度が最大値に達した時
g_visibleGraphic [i]. bright = GRAPHIC_MAX_BRIGHT ;// フェードイン解除
g_visibleGraphic [i]. mode = GRAPHIC_MODE_NONE ;
}
}

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

// フェードアウトを行うとき
if( g_visibleGraphic [i]. mode == GRAPHIC_MODE_FADEOUT ) {// 輝度を下げる
g_visibleGraphic [i]. bright -= GRAPHIC_FADEIN_FADEOUT_SPEED ;if( g_visibleGraphic [i]. bright <= 0 ) {// 画像を画面から消す
g_visibleGraphic [i]. graphicHandle = 0;
g_visibleGraphic [i]. graphicId = 0;
g_visibleGraphic [i].x = 0;
g_visibleGraphic [i].y = 0;
g_visibleGraphic [i]. bright = 0;
g_visibleGraphic [i]. mode = GRAPHIC_MODE_NONE ;continue ;
}
}

223
224
225
226
227
228

// 輝度セット
SetDrawBright ( g_visibleGraphic [i]. bright ,g_visibleGraphic [i]. bright , g_visibleGraphic [i]. bright );// アルファブレンドを行う
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , g_visibleGraphic [i]. bright ) ;

229
230
231
232
233
234
235
236

// 指定した座標に画像描画
DrawGraph ( g_visibleGraphic [i].x, g_visibleGraphic [i].y,g_visibleGraphic [i]. graphicHandle , TRUE );}}// 輝度情報をデフォルトに戻しておく
SetDrawBright ( GRAPHIC_MAX_BRIGHT , GRAPHIC_MAX_BRIGHT , GRAPHIC_MAX_BRIGHT );94

第 7 章 グラフィック管理// ブレンドモードを元にもどしておく
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , GRAPHIC_MAX_BRIGHT );

237
238
239

}

240
241
242
243
244
245
246
247
248
249
250
251
252

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 640 * 480
SetGraphMode ( 640 , 480 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

253
254
255

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK ) ;

256
257
258

// 初期化処理
initGraphicNode ();

259
260
261
262
263

// 画像追加
addGraphicNode (1, "./ pic/ kaeru1 .png");
addGraphicNode (2, "./ pic/ kaeru2 .png");
addGraphicNode (3, "./ pic/ kaeru2 .png");

264
265
266
267
268
269
270
271
272
273
274
275

// 表示する画像追加
addVisibleGraphic (1, 1, 10, 10);
addVisibleGraphic (2, 2, 50, 10);
addVisibleGraphic (3, 3, 50, 50);
addVisibleGraphic (1, 4, 100, 50);
addVisibleGraphic (1, 5, 200, 50);
addVisibleGraphic (1, 6, 300, 50);
addVisibleGraphic (1, 7, 100, 200);
addVisibleGraphic (1, 8, 200, 200);
addVisibleGraphic (1, 9, 300, 200);
addVisibleGraphic (2, 10 ,350 , 300);

276
277
278
279

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {280

if( CheckHitKey ( KEY_INPUT_F1 ) ) {// 画像を削除
removeVisibleGraphic (4);removeVisibleGraphic (5);removeVisibleGraphic (6);removeVisibleGraphic (7);removeVisibleGraphic (8);removeVisibleGraphic (9);removeVisibleGraphic (10);
}

281
282
283
284
285
286
287
288
289
290
291

// 画面クリア
ClearDrawScreen ();

292
293
294

// 画像描画
drawVisibleGraphic ();

295
296
297

Sleep (50);

298
299

ScreenFlip ();

300
301

}

95

7.7 グラフィック管理プログラムの改良
302

DxLib_End ();return 0;

303
304
305

}

実行結果は図 7.9 となります．

図 7.9: グラフィック管理プログラム

7.7

グラフィック管理プログラムの改良

グラフィック管理プログラムは前節で完成しました．しかし，§メモリ上に画像を読み込むの部分でも少し説明しましたが，このような実装では，ノベルゲームの規模が大きくなった場合，処理速度の低下等の問題が表れてくるかもしれません．
この節では，これらの問題をどのように解決したら良いのか，どのようなアルゴリズムを使ったら良いのかについて解説していきます．なお，アルゴリズムの実装方法等の詳細については，アルゴリズムの部をご覧ください．
GraphicNode や VisibleGraphicNode 構造体を管理する際，今回は配列を利用しました．
しかし，配列で実装した際，要素数が固定されてしまうなどの問題があります．例えば，

96

第 7 章 グラフィック管理
今回の場合は GraphicNode に登録できる要素数は最大 3 つとなっています．よって，それ以上の画像を登録することはできないのです．
まあ，要素数を増やせば，登録できる画像数も増えるわけですが，増やしすぎても色々と問題が発生してきます．例えば，最大要素数を 10000 と設定しておくとしましょう．こ
うしておけば，登録できる画像数は 10000 個です．しかし，実際には画像を 10 個程度しか保存しなかった場合，残り 9990 個分のメモリが無駄となってしまいます．
また，データを登録する際，今回は先頭要素から順に空いている要素を探していくような実装としました．しかし，例えば空き要素が 1000 番目にあったとしたら，先頭要素から順に ID を比較していかなければならず，処理の効率がとても悪いです (図 7.10）．

図 7.10: 空き要素を探す場合

これらの問題を解決するには，連結リスト（リンクリスト）を利用すれば良いのです．
リンクリストは，要素の挿入を無制限に行うことができるもので，データをポインタで繋いだ構造をしています (図 7.11）．

図 7.11: リンクリスト

リンクリストにデータを追加するには，tail の 1 つ前に要素を付け加えればよいだけです (図 7.12）．
このような構造を用いれば，メモリ領域の無駄が無くなり，さらに，リストの最後尾の場所 (tail) さえ記憶しておけば，データの追加もらくらく行うことができます．
リンクリストにも問題はあります．例えば，指定した ID を探したい場合はどうでしょう
か．先頭要素から順に ID を検査していくことになり，これでは配列とあまり変わりません．この問題を解決するには，リンクリストを更に発展させた二分木（バイナリーツリー）97

7.7 グラフィック管理プログラムの改良

図 7.12: リンクリストへのデータの追加を使うなどの方法が考えられます．もちろん，他にも方法はありますが，今回は二分木を取り上げることとします．二分木は図 7.13 のような構造をしています．

図 7.13: 二分木（二分探索木）

図 7.13 は二分木を更に発展させた二分探索木という構造をしています．二分探索木は，あるノードの左側にある子孫ノードはそのノードの値よりも小さく，右側にある子孫ノードはそのノードの値よりも大きくなるように構成されています．
例えば，ID = 12 のノードを探したい場合は，まず根となるノードの値よりも小さいか大きいかを比較します．図 7.13 の場合，根のノードは 10 なので，ID = 12 は右側の子孫
ノードにあることが分かります．次に，ID = 15 を調べれば，ID = 12 は左側の子孫ノードにあることが分かります．このようなデータ構造を使うことで，データの検索を効率的に行うことができるようになります．
もちろん，二分探索木にも問題はあります．例えば，ID の値によっては，図 7.14 のような状態に陥ってしまうこともあります．これではリンクリストと変わりありません．
よって，ID の付け方にも色々と工夫が必要になってきます．こういった場合，平衡二分探索木等を利用する必要がでてきます．まあ，ここで平衡二分探索木の説明をするときり98

第 7 章 グラフィック管理

図 7.14: 連結リストと変わらない二分探索木が無いので，この辺でやめておきます．
さて，ここまでグラフィック管理プログラムの改良の方法について解説してきました．
読者の方の中には，難しかった，分けが分からなかった方もいるかと思います．そういった方は是非アルゴリズムの部を見てみて，データ構造等について勉強してみてください．
また，ここで学んだアルゴリズムを是非グラフィック管理のプログラムに適応してみて下
さい．

99

101

第 8 章 選択肢の表示
この章では，ノベルゲームに欠かせない選択肢表示部分を作っていきたいと思います．
条件分岐の数は 2 個までとします．選択肢が 2 つ画面に表示され，どちらをクリックしたかどうかをチェックするプログラムを作成していきます．完成イメージは図 8.1 となります．

図 8.1: 選択肢の表示

8.1

選択肢ボックスの表示

では，画面に選択肢ボックスを表示させるプログラムを作成してみましょう．難しいことは全くありません．ただ画面に選択肢ボックスの画像を貼り付け，その上に文字を描画しているだけです．

8.1 選択肢ボックスの表示
では，選択肢ボックスを表示するプログラムをお見せします．利用する画像は back.pngと select.png です．back.png は背景画像です．また，select.png は図 8.2 を利用することとします．

図 8.2: select.png

リスト 8.1: ”selection-01.cpp”
1# include "DxLib .h"2
3
4# define GRAPHIC_BACKGROUND_FILENAME "./ pic/back.png"# define GRAPHIC_SELECTBOX_FILENAME "./ pic/ select .png"

567# define SELECT_BOX_X 50# define SELECT_BOX_Y 200

8910# define SELECT_BOX_WIDTH 700# define SELECT_BOX_HEIGHT 50

1112# define SELECT_BOX_MESSAGE_Y 20

13
14# define FONT_SIZE 16

15
16
17
18
19
20
21
22
23
24
25
26

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

27
28
29

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );

30
31
32

// フォントの大きさをセット
SetFontSize ( FONT_SIZE );

33
34
35
36
37
38
39

// 白
int whiteColor = GetColor (255 , 255 , 255);// 背景の読み込み
int backgroundGraphic = LoadGraph ( GRAPHIC_BACKGROUND_FILENAME );// 選択ボックスの読み込み
int selectBoxGraphicHandle = LoadGraph ( GRAPHIC_SELECTBOX_FILENAME );

40
41
42

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

43
44
45

// 画面クリア
ClearDrawScreen ();

46
47
48
49

// 背景を描画
DrawGraph ( 0, 0, backgroundGraphic , FALSE );// 透明度 50 ％ と す る102

第 8 章 選択肢の表示
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128 );// 選択ボックスの描画 ( 選 択 肢 2 つ の 場 合 )DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y , selectBoxGraphicHandle , TRUE );DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,selectBoxGraphicHandle , TRUE );// アルファブレンドを元に戻す
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

50
51
52
53
54
55
56
57

// 適当に文字を表示する
DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_MESSAGE_Y ," 選択肢 1 あ い う え お か き く け こ ", whiteColor );DrawString ( SELECT_BOX_X + 20,
SELECT_BOX_Y + SELECT_BOX_HEIGHT + SELECT_BOX_MESSAGE_Y ," 選択肢 2 さ し す せ そ た ち つ て と ", whiteColor );

58
59
60
61
62
63
64

ScreenFlip ();

65

}

66
67

DxLib_End ();return 0;

68
69
70

}

選択肢ボックスの画像 (select.png) を表示する際には，アルファブレンドを利用して半透明にしています．実行結果は図 8.3 となります．

図 8.3: 選択肢ボックスの表示

103

8.2 マウスカーソルがボックス内に存在するかの判定

8.2

マウスカーソルがボックス内に存在するかの判定

選択肢の上にマウスカーソルを乗せた時，選択肢ボックスの透明度を無くすプログラムを作成してみたいと思います．マウスカーソルの座標を取得するには，GetMousePoint 関数を利用します．

int GetMousePoint( int *XBuf, int *YBuf );XBuf, YBuf にはマウスカーソルの座標が代入されます．
さて，選択肢ボックスを四角形として，マウスカーソルを点とします．マウスカーソル
が選択肢ボックス内にあるかどうかは，点が四角形内に存在しているかどうかの判定となります．ここで，点の座標を (x1 , y1 ), 四角形の左端の座標を (x2 , y2 ), 高さを height, 横幅を
width とすると，
x1 ≥ x2
x1 ≤ x2 + widthy1 ≥ y2
y1 ≤ y2 + heightを満たせば，点が四角形内にあると判定できます (図 8.4）．

図 8.4: 点が四角形内に存在するかの判定

この判定をプログラムにすると，以下のようになります．
1
2
3
4
5

//指定したボックス内にマウスが存在するかどうか//戻り値 1:存在する 0:存在しない
int isContainMousePointer(int x, int y, int width, int height){
int mouseX, mouseY;6104

第 8 章 選択肢の表示//マウスの座標を取得
GetMousePoint( &mouseX, &mouseY );789//ボックス内にマウス座標が存在するか
if( (mouseX >= x && mouseX <= x + width) &&(mouseY >= y && mouseY <= y + height) ) {return 1;}
return 0;

10
11
12
13
14
15

}

16

これらを踏まえて，選択肢ボックス上にマウスを乗せた場合，ボックスの透明度を変えるプログラムを作成してみましょう．また，前回のプログラムは main 関数に色々と処理を書きすぎました．今回は main 関数で行っていた描画処理を，drawSelectBox 関数へと移動させました．
リスト 8.2: ”selection-02.cpp”
1# include "DxLib.h"2
3
4# define GRAPHIC_BACKGROUND_FILENAME "./ pic/back.png"# define GRAPHIC_SELECTBOX_FILENAME "./ pic/ select .png"

567# define SELECT_BOX_X 50# define SELECT_BOX_Y 200

8910# define SELECT_BOX_WIDTH 700# define SELECT_BOX_HEIGHT 50

1112# define SELECT_BOX_MESSAGE_Y 20

13
14# define FONT_SIZE 16

15
16
17
18
19
20
21
22

// 選択肢ボックス関係// 白
int g_whiteColor ;// 背景の読み込み
int g_backgroundGraphic ;// 選択ボックスの読み込み
int g_selectBoxGraphicHandle ;

23
24
25
26

// プロトタイプ宣言
int isContainMousePointer (int x, int y, int width , int height );void drawSelectBox ();

27
28
29
30
31
32

// 指定したボックス内にマウスが存在するかどうか// 戻り値 1: 存 在 す る 0: 存 在 し な い
int isContainMousePointer (int x, int y, int width , int height ){int mouseX , mouseY ;

33
34
35

// マウスの座標を取得
GetMousePoint ( &mouseX , & mouseY );

36
37
38
39
40

// ボックス内にマウス座標が存在するか
if( ( mouseX >= x && mouseX <= x + width) &&( mouseY >= y && mouseY <= y + height ) ) {return 1;

105

8.2 マウスカーソルがボックス内に存在するかの判定}
return 0;

41
42
43

}

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

// 選択肢ボックスを描画する
void drawSelectBox (){// 選択肢ボックス 1 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックスの描画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y , g_selectBoxGraphicHandle , TRUE );59// 選択肢ボックス 2 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックス 2 つ 目 の 描 画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,g_selectBoxGraphicHandle , TRUE );

60
61
62
63
64
65
66
67
68
69
70
71
72

// アルファブレンドを元に戻す
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

73
74
75

// 適当に文字を表示する
DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_MESSAGE_Y ," 選択肢 1 あ い う え お か き く け こ ", g_whiteColor );DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_HEIGHT + SELECT_BOX_MESSAGE_Y ," 選択肢 2 さ し す せ そ た ち つ て と ", g_whiteColor );

76
77
78
79
80
81

}

82
83
84
85
86
87
88
89
90
91
92
93

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

94
95
96

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );

97
98
99

// フォントの大きさをセット
SetFontSize ( FONT_SIZE );

100
101
102

// マ ウ ス を 表 示 状 態 に す る
SetMouseDispFlag ( TRUE );

103
104
105

// 白
g_whiteColor = GetColor (255 , 255 , 255);

106

第 8 章 選択肢の表示// 背景の読み込み
g_backgroundGraphic = LoadGraph ( GRAPHIC_BACKGROUND_FILENAME );// 選択ボックスの読み込み
g_selectBoxGraphicHandle = LoadGraph ( GRAPHIC_SELECTBOX_FILENAME );

106
107
108
109
110

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

111
112
113

// 画面クリア
ClearDrawScreen ();

114
115
116

// 背景を描画
DrawGraph ( 0, 0, g_backgroundGraphic , FALSE );

117
118
119

// 選択肢ボックスの描画
drawSelectBox ();

120
121
122

ScreenFlip ();

123

}

124
125

DxLib_End ();return 0;

126
127
128

}

このプログラムの実行結果は図 8.5 となりました．

図 8.5: 選択肢 1 にマウスカーソルを乗せた時

107

8.3 選択肢ボックスにメッセージを入れる

8.3

選択肢ボックスにメッセージを入れる

選択肢ボックスに，好きなメッセージを入れられるように改良してみましょう．選択肢
ボックスに表示したいメッセージを g selectBoxMessage にセットし，その文字列をただ描画するだけです．特に難しいことはありません．
また，メッセージが何もセットされていない時は，選択肢ボックスを表示しないようにしてみます．選択肢を表示するかどうかは g selectBoxVisibleFlag で管理することとしま
しょう．
では作成したプログラムをお見せします．F1,F2 キーを押すと，それぞれ違うメッセー
ジの入った選択肢ボックスが表示されます．
リスト 8.3: ”selection-03.cpp”
1# include "DxLib .h"2
3
4# define GRAPHIC_BACKGROUND_FILENAME "./ pic/back.png"# define GRAPHIC_SELECTBOX_FILENAME "./ pic/ select .png"

567# define SELECT_BOX_X 50# define SELECT_BOX_Y 200

8910# define SELECT_BOX_WIDTH 700# define SELECT_BOX_HEIGHT 50

1112# define SELECT_BOX_MESSAGE_Y 20

13
14# define FONT_SIZE 16

15
16
17
18

# define SELECT_BOX_MESSAGE_MAX_LENGTH 100# define SELECT_BOX_HIDE 0# define SELECT_BOX_SHOW 1

19
20
21
22
23
24
25
26
27
28
29
30

// 選択肢ボックス関係 ( 選 択 肢 は 2 つ と す る )// 白
int g_whiteColor ;// 背景の読み込み
int g_backgroundGraphic ;// 選択ボックスの読み込み
int g_selectBoxGraphicHandle ;// 選択ボックスに表示するメッセージ
char g_selectBoxMessage [2][ SELECT_BOX_MESSAGE_MAX_LENGTH ];// 選択ボックスを表示するかどうか 0: 非 表 示 1: 表 示
int g_selectBoxVisibleFlag ;

31
32
33
34
35
36

// プロトタイプ宣言
void initSelectBox ();int isContainMousePointer (int x, int y, int width , int height );void drawSelectBox ();void setSelectBoxMessage (const char* message1 , const char* message2 );

37
38
39
40
41
42
43
44
45
46
47

// 選択ボックスの初期化
void initSelectBox (){// 白
g_whiteColor = GetColor (255 , 255 , 255);// 選択ボックスの読み込み
g_selectBoxGraphicHandle = LoadGraph ( GRAPHIC_SELECTBOX_FILENAME );// 選択ボックスのメッセージ初期化
memset ( g_selectBoxMessage [0], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );memset ( g_selectBoxMessage [1], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );108

第 8 章 選択肢の表示// 選択ボックスを表示しない
g_selectBoxVisibleFlag = SELECT_BOX_HIDE ;

48
49
50

}

51
52
53
54
55
56
57

// 指定したボックス内にマウスが存在するかどうか// 戻り値 1: 存 在 す る 0: 存 在 し な い
int isContainMousePointer (int x, int y, int width , int height ){int mouseX , mouseY ;58// マウスの座標を取得
GetMousePoint ( &mouseX , & mouseY );

59
60
61

// ボックス内にマウス座標が存在するか
if( ( mouseX >= x && mouseX <= x + width) &&( mouseY >= y && mouseY <= y + height ) ) {return 1;}
return 0;

62
63
64
65
66
67
68

}

69
70
71
72
73
74
75
76
77
78

// 選択ボックスにメッセージをセットする
void setSelectBoxMessage (const char* message1 , const char* message2 ){// メッセージセット
strncpy ( g_selectBoxMessage [0], message1 , SELECT_BOX_MESSAGE_MAX_LENGTH );strncpy ( g_selectBoxMessage [1], message2 , SELECT_BOX_MESSAGE_MAX_LENGTH );// 選択ボックスを表示
g_selectBoxVisibleFlag = SELECT_BOX_SHOW ;
}

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

// 選択肢ボックスを描画する
void drawSelectBox (){// 選択ボックスを表示するかどうか
if( g_selectBoxVisibleFlag ) {// 選択肢ボックス 1 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックスの描画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y , g_selectBoxGraphicHandle , TRUE );

96
97
98
99
100
101
102
103
104
105
106
107
108

// 選択肢ボックス 2 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックス 2 つ 目 の 描 画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,g_selectBoxGraphicHandle , TRUE );

109
110
111

// アルファブレンドを元に戻す
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

112

109

8.3 選択肢ボックスにメッセージを入れる// メッセージ表示
DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_MESSAGE_Y ,g_selectBoxMessage [0], g_whiteColor );DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_HEIGHT + SELECT_BOX_MESSAGE_Y ,g_selectBoxMessage [1], g_whiteColor );

113
114
115
116
117

}

118
119

}

120
121
122
123
124
125
126
127
128
129
130
131

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;}

132// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );

133
134
135

// フォントの大きさをセット
SetFontSize ( FONT_SIZE );

136
137
138

// マ ウ ス を 表 示 状 態 に す る
SetMouseDispFlag ( TRUE );

139
140
141

// 選択ボックスの初期化
initSelectBox ();// 背景の読み込み
g_backgroundGraphic = LoadGraph ( GRAPHIC_BACKGROUND_FILENAME );

142
143
144
145
146

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {

147
148
149

// F 1 ま た は F 2 を 押 す と 選 択 ボ ッ ク ス を 表 示
if( CheckHitKey ( KEY_INPUT_F1 ) ) {setSelectBoxMessage (" F 1 を 押 し た 時 の 選 択 肢 1 ", " 選択肢 2");}else if( CheckHitKey ( KEY_INPUT_F2 ) ) {setSelectBoxMessage (" F 2 を 押 し た 時 の 選 択 肢 1 ", " 選択肢 2 あ い う え お ");}// 画面クリア
ClearDrawScreen ();

150
151
152
153
154
155
156
157
158

// 背景を描画
DrawGraph ( 0, 0, g_backgroundGraphic , FALSE );

159
160
161

// 選択肢ボックスの描画
drawSelectBox ();

162
163
164

ScreenFlip ();

165

}

166
167

DxLib_End ();return 0;

168
169
170

}

プログラムの実行結果は図 8.6 及び図 8.7 となります．

110

第 8 章 選択肢の表示

図 8.6: F1 を押した時

図 8.7: F2 を押した時

111

8.4 選択肢の表示プログラム

8.4

選択肢の表示プログラム

ようやく選択肢表示のプログラムの完成です．今回は，選択肢をクリックできるようにしてみましょう．どの選択肢がクリックされたかを調べる為に，isClickedSelectBox 関数を作成しました．ここで，マウスが押されているかどうかを調べるには，GetMouseInput 関
数を利用します．

int GetMouseInput( void );この関数は，戻り値としてマウスの入力状態を返します．マウスのどのボタンが押されているかを調べるには，以下の定義値と AND 演算を行うことで調べることができます．

• MOUSE INPUT LEFT : マウス左ボタン• MOUSE INPUT RIGHT : マウス右ボタン• MOUSE INPUT MIDDLE : マウス中央ボタン
例えば，マウス左ボタンが押されているかどうかを判定するには，以下のようなプログ
ラムを書けばいいわけです．

if( ( GetMouseInput() & MOUSE_INPUT_LEFT ) != 0 ) {//左クリック時の動作}
では，完成したプログラムをお見せします．
リスト 8.4: ”selection-04.cpp”
1# include "DxLib .h"2
3
4# define GRAPHIC_BACKGROUND_FILENAME "./ pic/back.png"# define GRAPHIC_SELECTBOX_FILENAME "./ pic/ select .png"

567# define SELECT_BOX_X 50# define SELECT_BOX_Y 200

8910# define SELECT_BOX_WIDTH 700# define SELECT_BOX_HEIGHT 50

1112# define SELECT_BOX_MESSAGE_Y 20

13
14# define FONT_SIZE 16

15
16
17
18

# define SELECT_BOX_MESSAGE_MAX_LENGTH 100# define SELECT_BOX_HIDE 0# define SELECT_BOX_SHOW 1

19
20
21
22
23
24
25
26
27

// 選択肢ボックス関係 ( 選 択 肢 は 2 つ と す る )// 白
int g_whiteColor ;// 背景の読み込み
int g_backgroundGraphic ;// 選択ボックスの読み込み
int g_selectBoxGraphicHandle ;// 選択ボックスに表示するメッセージ112

第 8 章 選択肢の表示

28
29
30

char g_selectBoxMessage [2][ SELECT_BOX_MESSAGE_MAX_LENGTH ];// 選択ボックスを表示するかどうか 0: 非 表 示 1: 表 示
int g_selectBoxVisibleFlag ;

31
32
33
34
35
36
37

// プロトタイプ宣言
void initSelectBox ();int isContainMousePointer (int x, int y, int width , int height );void drawSelectBox ();void setSelectBoxMessage (const char* message1 , const char* message2 );int isClickedSelectBox ();

38
39
40
41
42
43
44
45
46
47
48
49
50
51

// 選択ボックスの初期化
void initSelectBox (){// 白
g_whiteColor = GetColor (255 , 255, 255);// 選択ボックスの読み込み
g_selectBoxGraphicHandle = LoadGraph ( GRAPHIC_SELECTBOX_FILENAME );// 選択ボックスのメッセージ初期化
memset ( g_selectBoxMessage [0], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );memset ( g_selectBoxMessage [1], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );// 選択ボックスを表示しない
g_selectBoxVisibleFlag = SELECT_BOX_HIDE ;
}

52
53
54
55
56
57
58

// 指定したボックス内にマウスが存在するかどうか// 戻り値 1: 存 在 す る 0: 存 在 し な い
int isContainMousePointer (int x, int y, int width , int height ){int mouseX , mouseY ;59// マウスの座標を取得
GetMousePoint ( &mouseX , & mouseY );

60
61
62

// ボックス内にマウス座標が存在するか
if( ( mouseX >= x && mouseX <= x + width) &&( mouseY >= y && mouseY <= y + height ) ) {return 1;}
return 0;

63
64
65
66
67
68
69

}

70
71
72
73
74
75
76
77
78
79

// 選択ボックスにメッセージをセットする
void setSelectBoxMessage (const char* message1 , const char* message2 ){// メッセージセット
strncpy ( g_selectBoxMessage [0], message1 , SELECT_BOX_MESSAGE_MAX_LENGTH );strncpy ( g_selectBoxMessage [1], message2 , SELECT_BOX_MESSAGE_MAX_LENGTH );// 選択ボックスを表示
g_selectBoxVisibleFlag = SELECT_BOX_SHOW ;
}

80
81
82
83
84
85
86
87
88
89
90
91
92

// 選択肢ボックスを描画する
void drawSelectBox (){// 選択ボックスを表示するかどうか
if( g_selectBoxVisibleFlag ) {// 選択肢ボックス 1 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る113

8.4 選択肢の表示プログラム
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックスの描画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y , g_selectBoxGraphicHandle , TRUE );

93
94
95
96
97

// 選択肢ボックス 2 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 透明度 0 ％ と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 255 );}else {// 透明度 50% と す る
SetDrawBlendMode ( DX_BLENDMODE_ALPHA , 128);}// 選択ボックス 2 つ 目 の 描 画
DrawGraph ( SELECT_BOX_X , SELECT_BOX_Y + SELECT_BOX_HEIGHT ,g_selectBoxGraphicHandle , TRUE );

98
99
100
101
102
103
104
105
106
107
108
109
110

// アルファブレンドを元に戻す
SetDrawBlendMode ( DX_BLENDMODE_NOBLEND , 0 );

111
112
113

// メッセージ表示
DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_MESSAGE_Y ,g_selectBoxMessage [0], g_whiteColor );DrawString ( SELECT_BOX_X + 20, SELECT_BOX_Y + SELECT_BOX_HEIGHT + SELECT_BOX_MESSAGE_Y ,g_selectBoxMessage [1], g_whiteColor );

114
115
116
117
118

}

119
120

}

121
122
123
124
125
126
127
128
129
130

// 選択肢ボックスがクリックされたかどうか//0: ク リ ッ ク さ れ て い な い 1: 選 択 肢 1 が ク リ ッ ク さ れ た 2: 選 択 肢 2 が ク リ ッ ク さ れ た
int isClickedSelectBox (){// 選択肢ボックスが表示されているとき
if( g_selectBoxVisibleFlag ) {// マウスの状態を調べる// 左クリックされたときif( ( GetMouseInput () & MOUSE_INPUT_LEFT ) != 0 ) {131

if( isContainMousePointer ( SELECT_BOX_X , SELECT_BOX_Y ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 選択肢ボックス 1 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合// 選択ボックスのメッセージ初期化
memset ( g_selectBoxMessage [0], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );memset ( g_selectBoxMessage [1], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );// 選択ボックスを表示しない
g_selectBoxVisibleFlag = SELECT_BOX_HIDE ;// 選択肢 1 が ク リ ッ ク さ れ て い る こ と を 通 知
return 1;}else if( isContainMousePointer ( SELECT_BOX_X ,SELECT_BOX_Y + SELECT_BOX_HEIGHT ,SELECT_BOX_WIDTH , SELECT_BOX_HEIGHT ) ) {// 選択肢ボックス 2 が マ ウ ス ポ イ ン タ を 含 ん で い た 場 合// 選択ボックスのメッセージ初期化
memset ( g_selectBoxMessage [0], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );memset ( g_selectBoxMessage [1], 0, SELECT_BOX_MESSAGE_MAX_LENGTH );// 選択ボックスを表示しない
g_selectBoxVisibleFlag = SELECT_BOX_HIDE ;// 選択肢 2 が ク リ ッ ク さ れ て い る こ と を 通 知
return 2;
}

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

}
}
return 0;

154
155
156
157

}

114

第 8 章 選択肢の表示

158
159
160
161
162
163
164
165
166
167
168
169
170

int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance ,LPSTR lpCmdLine , int nCmdShow ){// ウィンドウモードで起動
ChangeWindowMode ( TRUE );// 画面の大きさは 800 * 600
SetGraphMode ( 800, 600 , 16 ) ;// D x L i b 初 期 化
if( DxLib_Init () == -1 ) {return -1;
}

171
172
173

// 描 画 先 を 裏 画 面 に セ ッ ト
SetDrawScreen ( DX_SCREEN_BACK );

174
175
176

// フォントの大きさをセット
SetFontSize ( FONT_SIZE );

177
178
179

// マ ウ ス を 表 示 状 態 に す る
SetMouseDispFlag ( TRUE );

180
181
182
183
184

// 選択ボックスの初期化
initSelectBox ();// 背景の読み込み
g_backgroundGraphic = LoadGraph ( GRAPHIC_BACKGROUND_FILENAME );

185
186

int tmpClicked = 0; // テスト用

187
188
189

// メインループ
while( ProcessMessage () == 0 && CheckHitKey ( KEY_INPUT_ESCAPE ) == 0 ) {190// F 1 を 押 す と 選 択 ボ ッ ク ス を 表 示
if( CheckHitKey ( KEY_INPUT_F1 ) ) {setSelectBoxMessage (" F 1 を 押 し た 時 の 選 択 肢 1 ", " 選択肢 2");
}

191
192
193
194
195

// 画面クリア
ClearDrawScreen ();

196
197
198

// 背景を描画
DrawGraph ( 0, 0, g_backgroundGraphic , FALSE );

199
200
201

// 選択肢ボックスの描画
drawSelectBox ();

202
203
204

// どの選択肢がクリックされたか調べる
tmpClicked = isClickedSelectBox ();if( tmpClicked == 1 ) {DrawString (50, 50, " 選択肢 1 が ク リ ッ ク さ れ ま し た ", g_whiteColor );}else if( tmpClicked == 2 ) {DrawString (50, 50, " 選択肢 2 が ク リ ッ ク さ れ ま し た ", g_whiteColor );
}

205
206
207
208
209
210
211
212

ScreenFlip ();

213
214

// クリックされたことが分かるようにしておく
if( tmpClicked != 0 ) {Sleep (1000);
}

215
216
217
218
219

}

220
221
222

DxLib_End ();return 0;115

8.4 選択肢の表示プログラム
223}

このプログラムの実行結果は図 8.8 となります．

図 8.8: 選択肢の表示プログラム

116

117

第 9 章 ノベルゲーム用スクリプト言語の作成
この章では，ノベルゲーム用スクリプトエンジンを作成していきたいと思います．ノベ
ルゲームを作るにあたっては，ここが一番の難所です．
なぜこのようにノベルゲーム用の言語を作成するのでしょうか．ストーリーなり何なり
C 言語のソース内に組み込んでしまえば，このようなスクリプト言語を作る必要はありま
せん．しかし，C 言語上にストーリーを書いた場合，ストーリーを変更するごとに，いちいちコンパイルしなおす必要があります．また，プログラム担当とシナリオ担当で作業を分担している場合，C 言語だけで作った場合は，シナリオ担当の人がいちいちプログラムの仕組みまで意識する必要がでてきます．
こういった問題を排除する為にノベルゲーム用のスクリプト言語を作っていきたいと思います．スクリプト言語には，次の機能を持たせることとします．

• メッセージの表示• 画像の読み込み・表示• 条件分岐なお，音楽再生，セーブ，フラグ等の機能は今回は作らないこととします．しかし，この章の内容を理解できれば，これらの機能は簡単に追加できるはずです．

9.1

スクリプトを読み込む

スクリプトエンジンは，スクリプトファイルから命令を読み込み，その内容を解析する役割を果たします（図 9.1）．

図 9.1: スクリプトエンジンの仕組み

9.1 スクリプトを読み込む
今回は，スクリプトファイルから文章を読み込むプログラムを作りましょう．文章は一行単位で読み取ることとします．また，ScriptInformation と呼ばれる構造体を作成し，この中に，読み取ったスクリプト，ファイル名，スクリプト行数等の情報を格納することとしましょう．
では，作成したプログラムをお見せします．
リスト 9.1: ”script-01.cpp”
1
2# include <stdio .h># include <string .h>

3
4
5
6
7

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

8
9
10
11
12
13
14

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

15
16
17
18
19
20
21
22
23

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){int pos;char c;// スクリプトファイル
FILE* fp;

24
25
26

// スクリプト情報を初期化
memset ( scriptInfo , 0, sizeof ( ScriptInformation ) );

27
28
29
30
31
32
33
34

// スクリプトファイルを開く
fp = fopen(filename , "r");if( fp == NULL ) {// ファイル読み込みに失敗
printf (" スクリプト %s を 読 み 込 め ま せ ん で し た \ n", filename );return -1;
}

35
36
37

// s c r i p t 書 き 込 み 時 に 使 用
pos = 0;

38
39
40
41
42
43
44
45

for( ;; ) {// 一文字読み込み
c = fgetc( fp );// ファイルの終わりかどうか
if( feof( fp ) ) {break;
}

46
47
48
49
50
51

if( pos >= SCRIPT_MAX_STRING_LENGTH - 1 ) {//1 行 の 文 字 数 が 多 す ぎ る
printf ("error: 文 字 数 が 多 す ぎ ま す (% d 行 目 )", scriptInfo -> currentLine );return -1;
}

52
53
54
55

// 改行文字が出てきた場合，次の行へ移動
if( c == ’\n’ ) {//\0 を 文 字 列 の 最 後 に 付 け る118

第 9 章 ノベルゲーム用スクリプト言語の作成
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = ’\0 ’;// 次の行に移動
scriptInfo -> currentLine ++;// 書き込み位置を 0 に す る
pos = 0;}else {// 書き込み
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = c;// 文字書き込み位置をずらす
pos ++;
}

56
57
58
59
60
61
62
63
64
65
66

}
// 最大行数
scriptInfo -> maxLineNumber = scriptInfo -> currentLine ;// 読み込み中の行を 0 に す る
scriptInfo -> currentLine = 0;// スクリプトファイル名を設定
scriptInfo -> filename = filename ;return 0;

67
68
69
70
71
72
73
74

}

75
76

int main (){int i;
ScriptInformation script ;

77
78
79
80
81

loadScript ( "./ script .txt", & script );82
83//10 行 目 ま で を 表 示
for( i = 0; i < 10; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );
}

84
85
86
87
88

return 0;

89

}

90

特に難しいことはありませんね．ただ一文字ずつ読み取って，ScriptInformation の scriptに書き込んでいるだけです．また，改行文字 (\n) を見つけたら，次の行に移動するようにしています．
このプログラムの実行結果は以下のようになりました．なお，読み込んだ script.txt の内
容は次のようになっています．
1@@message こんにちは，文章表示のテストです2
3@@message あいうえおかきくけこさしすせそ

1199.2 空白空行を飛ばして読み込む
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです2 :3 : @@message あいうえおかきくけこさしすせそ
4 :
5 :
6 :
7 :
8 :
9 :
10 :

µ

9.2

空白空行を飛ばして読み込む

前回のプログラムを少し改良して，空白，空行を読み飛ばすようにしてみましょう．今
回もたいしたことはありません．例えば，行の先頭の文字が空白又はタブ (\t) だった場
合は，文字がでてくるまで読み飛ばすようにしましょう．また，行の先頭文字が改行 (\n)
だった場合は，その行を読み飛ばすようにしましょう．
では，完成したプログラムをお見せします．
リスト 9.2: ”script-02.cpp”
1
2# include <stdio .h># include <string .h>

3
4
5
6
7

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

8
9
10
11
12
13
14

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

15
16
17
18
19
20
21
22
23

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){int pos;char c;// スクリプトファイル
FILE* fp;

24
25
26

// スクリプト情報を初期化
memset ( scriptInfo , 0, sizeof ( ScriptInformation ) );

27
28
29
30

// スクリプトファイルを開く
fp = fopen(filename , "r");if( fp == NULL ) {120´

第 9 章 ノベルゲーム用スクリプト言語の作成// ファイル読み込みに失敗
printf (" スクリプト %s を 読 み 込 め ま せ ん で し た \ n", filename );return -1;

31
32
33

}

34
35

// s c r i p t 書 き 込 み 時 に 使 用
pos = 0;36
3738for( ;; ) {// 一文字読み込み
c = fgetc( fp );// ファイルの終わりかどうか
if( feof( fp ) ) {break;}// 文章先頭の空白部分を読み飛ばす
while( (c == ’ ’ || c == ’\t’) && pos == 0 && !feof( fp ) ) {c = fgetc ( fp );
}

39
40
41
42
43
44
45
46
47
48
49
50

if( pos >= SCRIPT_MAX_STRING_LENGTH - 1 ) {//1 行 の 文 字 数 が 多 す ぎ る
printf ("error: 文 字 数 が 多 す ぎ ま す (% d 行 目 )", scriptInfo -> currentLine );return -1;
}

51
52
53
54
55
56

// 改行文字が出てきた場合，次の行へ移動
if( c == ’\n’ ) {// 空行は読み飛ばす
if( pos == 0 ) {continue ;}//\0 を 文 字 列 の 最 後 に 付 け る
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = ’\0 ’;// 次の行に移動
scriptInfo -> currentLine ++;// 書き込み位置を 0 に す る
pos = 0;}else {// 書き込み
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = c;// 文字書き込み位置をずらす
pos ++;
}

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

}
// 最大行数
scriptInfo -> maxLineNumber = scriptInfo -> currentLine ;// 読み込み中の行を 0 に す る
scriptInfo -> currentLine = 0;// スクリプトファイル名を設定
scriptInfo -> filename = filename ;return 0;

75
76
77
78
79
80
81
82
83

}

84
85
86
87
88

int main (){int i;
ScriptInformation script ;89
90

loadScript ( "./ script .txt", & script );

91
92
93
94
95

//10 行 目 ま で を 表 示
for( i = 0; i < 10; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );}121

9.3 文字列分割
96

return 0;

97

}

98

このプログラムの実行結果は以下のようになりました．また，読み込むスクリプトファ
イル script.txt の内容は以下のようにしてみました．

@@message こんにちは，文章表示のテストです1
2@@message あいうえおかきくけこさしすせそ

3
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです
2 : @@message あいうえおかきくけこさしすせそ
3 :
4 :
5 :
6 :
7 :
8 :
9 :
10 :

µ

9.3

´

文字列分割

今回は，文字列を指定した区切り文字で分割する関数を作りたいと思います．この関数は，次節で使用する部品となります．文字列を分割する関数は，Java 言語では StringTokenizerなどと呼ばれています．よく使いそうな機能なのですが，C 言語，C++言語ともに標準では搭載されていません．よって，自分で作る必要があります．
では，文字列を分割する splitString 関数を作成してみます．文字列分割には strtok 関数を利用しています．使い方が分からない方はインターネット等で調べてみてください．
リスト 9.3: ”script-03.cpp”
1
2
3# include <stdio .h># include <string .h># include <stdlib .h>456// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

7
8
9
10
11
12
13

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (char* src , char* dest [], const char* delim , int splitNum )122

第 9 章 ノベルゲーム用スクリプト言語の作成

14{
int i;
char* cp;
char* copySrc ;

15
16
17
18

// 元の文章をコピーする
copySrc = (char *) malloc ( sizeof (int) * SCRIPT_MAX_STRING_LENGTH + 1);
strncpy ( copySrc , src , SCRIPT_MAX_STRING_LENGTH );cp = copySrc ;

19
20
21
22
23

// s t r t o k を 使 っ て copySrc を d e l i m 区 切 り で 分 割 す る
for( i = 0; i < splitNum ; i++ ) {// 分割対象文字列が無くなるまで分割
if( (dest[i] = strtok (cp , delim )) == NULL ) {break;}//2 回 目 に s t r t o k を 呼 び 出 す 時 は ， c p を N U L L に す る
cp = NULL;}// 分割された文字列の最後の要素は N U L L と し て お く
dest[i] = NULL;

24
25
26
27
28
29
30
31
32
33
34
35

}

36
37
38
39
40
41
42
43
44
45

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){int i;
for( i = 0; elem[i] != NULL; i++ ) {printf ("%d : %s\n", i + 1, elem[i] );
}
}

46
47
48
49
50

int main (){char* dest [100];
const char* delim = " ";51

splitString (" @message test 222 333", dest , delim , 3);
printElements ( dest );

52
53
54

splitString (" @switch
printElements ( dest );55
56

test 333 444 555", dest , delim , 6);57

return 0;

58
59

}

プログラムの実行結果は以下のようになりました．空白区切りで文字列が分割されていることがわかります．

123

9.4 字句・構文解析器の作成
¶
実行結果

³

1 : @message
2 : test
3 : 222
1 : @switch
2 : test
3 : 333
4 : 4445 : 555µ9.4´

字句・構文解析器の作成

スクリプトエンジンは大まかに分けて，字句解析器と構文解析器からできています．字
句解析とは，連続した文字の並びを，字句（トークン）に分割することです．また，構文解析とは，字句解析によって分割された字句を取り出して，それらの並びから構造を把握し，実行すべき内容を判断するもののことです．
なにやら，難しいですね．まともなスクリプト言語を作ろうと思っているのなら，字句解析，構文解析だけでも作るのに相当の時間がかかるとおもいます．しかし，今回はゲーム用のスクリプト言語なので，難しい仕組みはすべて排除しましょう．例えば，画面にメッセージを表示するスクリプトの仕様は図 9.2 とします．これは，画面に「こんにちは，文章表示のテストです」と表示するスクリプトです．

図 9.2: メッセージ表示のスクリプト

スクリプト命令は，命令部，空白，命令の内容からできています．命令部はかならず行の先頭にくるようにしましょう．また，命令はすべて一文で終わるようにします．このようにすると制約は増えますが，設計が楽です．また，命令部と命令の内容は空白で区切る
こととしましょう．空白で区切ると，メッセージの内容をスクリプトの途中で改行できな
い，メッセージに空白文字を入れることができない等の制約がでてしまいますが，ここではこれらの問題は無視しましょう．
以上を踏まえると，字句解析器のする役割は，単にメッセージを空白で区切ればよいだ
けとなります．なんとも簡単な設計ですね．
次に構文解析器の説明に入ります．といっても，大それたものは作りません．むしろ，これから作るものは，構文解析器とはいえないようなものかもしれません．
@@message 命令を例に挙げて，構文解析の方法を説明していきます．字句解析を行うと，「@@message」と「表示したいメッセージ」の 2 つの字句に分割できます．そこで，1 番目

124

第 9 章 ノベルゲーム用スクリプト言語の作成の字句を命令とすることにしましょう．もし，ここに命令がこなかった場合はスクリプト
エラーとします．
2 番目以降の字句を命令文の引数としましょう．@@message 命令の場合，引数は 1 つしかとらないようにします．つまり，2 つ以上の引数が書かれていた場合は無視します．
以上のような設計にすると，処理はぐっと楽になります．もし，このようなプログラミ
ング言語を作ってしまったら誰も使いたがらないでしょうが，ゲーム用のスクリプト言語
なので，このくらいは気にしないこととしましょう．
さて，もう一つ命令を作ってみたいと思います．それは，指定した画像ファイルを座標(x, y) の位置に表示する命令，@@drawgraph です．@@drawgraph は引数を 3 つとります．第
1, 第 2 引数は画像を表示する座標 (x, y)，第 3 引数は表示する画像のファイル名を渡すこととします．例えば，human.png という画像を座標 (50, 100) に配置する場合，@@drawgraph 50 100 human.png
といったように記述することとします．
解説が少し長くなりました．では，作成したプログラムをお見せします．
リスト 9.4: ”script-04.cpp”
1
2
3# include <stdio.h># include <string .h># include <stdlib .h>

4
5
6
7
8

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

9
10
11
12
13
14
15

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

16
17
18
19
20
21
22

// プロトタイプ宣言
int loadScript (const char* filename , ScriptInformation * scriptInfo );void splitString (const char* src , char* dest [], const char* delim , int splitNum );void printElements (char* elem []);
int decodeScript (const char* scriptMessage );

23
24
25
26
27
28
29

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

30
31
32
33
34
35
36
37
38
39

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (const char* src , char* dest [], const char* delim , int splitNum ){// 省略（前回と同じ）}

125

9.4 字句・構文解析器の作成
40
41
42
43
44
45
46
47
48
49

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){int i;
for( i = 0; elem[i] != NULL; i++ ) {printf ("%d : %s\n", i + 1, elem[i] );
}
}

50
51
52
53
54
55
56
57
58

// スクリプト文を解読する// 戻り値 1: 成 功 0: 失 敗
int decodeScript (const char* scriptMessage ){// 分割されたスクリプト文
char* message [100];// 文字列分割時の区切り文字
const char* delim = " ";59// スクリプト分割
splitString ( scriptMessage , message , delim , 100 );

60
61
62

// 分割に失敗した場合
if( message [0] == NULL ) {return 0;
}

63
64
65
66
67

// s c r i p t の 仕 様//// @@message 文 字 列// --- 文 字 列 を メ ッ セ ー ジ と し て 表 示 す る//// @@drawgraph x y filename// --- f i l e n a m e で 指 定 し た 画 像 フ ァ イ ル を (x, y) の 位 置 に 表 示

68
69
70
71
72
73
74
75

// message [0] が @@message の 時 は ， メ ッ セ ー ジ 命 令 が 来 た と 判 断if( strncmp ( message [0], " @@message ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" メッセージ : %s\n", message [1] );return 1;}else if( strncmp ( message [0], " @@drawgraph ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" 画像 %s 表 示 -- x 座 標 : %d, y 座 標 : %d\n",message [3] , atoi( message [1] ), atoi( message [2] ) );return 1;
}

76
77
78
79
80
81
82
83
84
85

// 注意点 : エ ラ ー 処 理 を 行 っ て い な い ( message [2] ) が 存 在 し な か っ た 場 合 な ど// 例えば m e s s a g e の 全 要 素 を 全 て N U L L で 埋 め て お き ， message [2] が N U L L の 場 合 は// エラー処理を行うなどの手が考えられる

86
87
88
89

printf (" スクリプトエラー \n");return 0;

90
91
92

}

93
94
95
96
97

int main (){int i;
ScriptInformation script ;98
99

loadScript ( "./ script .txt", & script );

100
101
102
103
104

//10 行 目 ま で を 表 示
for( i = 0; i < 10; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );}126

第 9 章 ノベルゲーム用スクリプト言語の作成
for( i = 0; decodeScript ( script . script [i] ) != 0 ; i++ )
;

105
106
107

return 0;

108

}

109

実行結果は以下のようになりました．なお，使用した script.txt は以下のような内容となっています．

@@message こんにちは，文章表示のテストです hello12
3@@message あいうえおかきくけこ

45@@drawgraph 50 100 human.png
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです hello
2 : @@message あいうえおかきくけこ
3 : @@drawgraph 50 100 human.png
4 :
5 :
6 :
7 :
8 :
9 :
10 :
メッセージ : こんにちは，文章表示のテストですメッセージ : あいうえおかきくけこ
画像 human.png 表示 -- x 座標 : 50, y 座標 : 100µ´

script.txt の一行目の文章は hello の部分が正しく表示されていないようです．これは，メッセージの途中で空白が含まれているからでしょう．しかし，メッセージの内容には空白を入れないようにするとして，今回はこれで妥協することとしましょう．
@@message 命令も@@drawgraph 命令もうまく動いているようですね．

9.5

ラベルの検索C 言語の文法の一つに goto 命令というものがあります．これは，指定したラベルへジャンプする命令です．例えば，次のような C 言語コードを書いてみます．
1
2
3

int main(void){
printf("hello\n");127

9.5 ラベルの検索
goto NEXT;4
5

printf("ここは実行されない\n");

6

NEXT:

7

printf("next へジャンプしました\n");return 0;

8
9

}

10

上記のコードの NEXT: という部分がラベルにあたります．goto NEXT; は NEXT ラベ
ルまでジャンプするということです．このプログラムの実行結果は以下のようになります．
¶
実行結果

³

hello
next へジャンプしましたµ´C 言語の goto 命令は特別な事情を除いて使うべきではないでしょう．それは，プログラ
ムの流れが分かりにくくなるからです．しかし，今回作成するスクリプト言語では，このgoto 命令を採用する事とします．理由は，実装が簡単だからです．
さて，今回はこの goto 命令に利用するラベルが，何行目に存在しているかを検索するプ
ログラムを作って行きたいと思います．ラベルの定義は，@@label ラベル名としましょう．例えば，ラベル NEXT を定義したい場合は，@@label NEXTとします．
では，作成したプログラムをお見せします．searchScriptLabel 関数は，指定したラベルが何行目に存在するかを調べる関数です．
リスト 9.5: ”script-05.cpp”
1
2
3# include <stdio .h># include <string .h># include <stdlib .h>

4
5
6
7
8

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

9
10
11
12
13
14
15

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

16
17
18
19

// プロトタイプ宣言
int loadScript (const char* filename , ScriptInformation * scriptInfo );128

第 9 章 ノベルゲーム用スクリプト言語の作成

20
21
22

void splitString (const char* src , char* dest [], const char* delim , int splitNum );void printElements (char* elem []);
int searchScriptLabel (const char* label , ScriptInformation * scriptInfo );

23
24
25
26
27
28
29

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

30
31
32
33
34
35
36
37
38
39

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (const char* src , char* dest [], const char* delim , int splitNum ){// 省略（前回と同じ）
}

40
41
42
43
44
45
46
47
48
49

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){int i;
for( i = 0; elem[i] != NULL; i++ ) {printf ("%d : %s\n", i + 1, elem[i] );
}
}

50
51
52
53
54
55
56
57
58
59

// 指定したラベルがある行数を探す// 戻り値 正 の 数 : 指 定 し た ラ ベ ル の 行 番 号 -1: エ ラ ー
int searchScriptLabel (const char* label , ScriptInformation * scriptInfo ){// 分割されたスクリプト文
char* message [100];// 文字列分割時の区切り文字
const char* delim = " ";int i, line = -1;

60

for( i = 0; i < scriptInfo -> maxLineNumber ; i++ ) {// スクリプト分割
splitString ( scriptInfo -> script [i] , message , delim , 100 );

61
62
63
64

// 分割に失敗した場合
if( message [0] == NULL ) {return -1;
}

65
66
67
68
69

// 指定したラベルを探す
if( strncmp ( message [0], " @@label ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {if( strncmp ( message [1], label , SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// 指定したラベルが見つかった時
line = i;break;
}
}

70
71
72
73
74
75
76
77

}

78
79

return line;

80
81

}

82
83
84

int main (){129

9.5 ラベルの検索
int i;
int line;
ScriptInformation script ;

85
86
87
88

loadScript ( "./ script .txt", & script );89
90

for( i = 0; i < script . maxLineNumber ; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );
}

91
92
93
94

line = searchScriptLabel (" LABEL1 ", & script );printf (" LABEL1 は %d 行 目 に あ り ま す \ n", line + 1);

95
96
97

line = searchScriptLabel ("NEXT", & script );printf (" NEXT は %d 行 目 に あ り ま す \ n", line + 1);

98
99
100

line = searchScriptLabel ("HELLO ", & script );printf (" HELLO は %d 行 目 に あ り ま す \ n", line + 1);

101
102
103

return 0;

104

}

105

searchScriptLabel 関数は，ラベルを 1 行目から最後の行まで順に調べていくようにしています．しかし，このような仕様では，スクリプトの行数が長くなった時に処理速度が気になります．この問題の解決方法は，この章の最後のセクションにある改良のポイントをご覧ください．
実行結果は以下のようになりました．なお，使用した script.txt は以下のようになっています．
1@@message こんにちは，文章表示のテストです2
3@@label LABEL1

4
5
6@@message LABEL1 にジャンプしました@@goto NEXT

7
8@@label LABEL2

9
10
11

@@message LABEL2 にジャンプしました@@goto NEXT

12
13@@label NEXT14
15@@message これで終了します130

第 9 章 ノベルゲーム用スクリプト言語の作成
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです
2 : @@label LABEL1
3 : @@message LABEL1 にジャンプしました
4 : @@goto NEXT
5 : @@label LABEL2
6 : @@message LABEL2 にジャンプしました
7 : @@goto NEXT
8 : @@label NEXT
9 : @@message これで終了します
LABEL1 は 2 行目にありますNEXT は 8 行目にあります
HELLO は 0 行目にあります
µ

9.6

´

条件分岐構文の作成

条件分岐用構文@@select を作成していきたいと思います．今回作成する条件分岐は，ゲーム側がユーザに質問をして，その回答によって処理を分岐させることです．例えば，ゲーム側がユーザに年齢を尋ねるとしましょう．条件はユーザが 20 歳未満か 20 歳以上かのどちらかとします．ここで，ユーザが 20 歳未満を選択した場合はラベル LABEL1 へ飛ぶ，20 歳以上を選択した場合はラベル LABEL2 へ飛ぶといった処理をするのが@@selectの役割です（図 9.4）．
@@select 構文の構造は図 9.3 としましょう．条件メッセージとジャンプするラベルは区
切り文字@@で区切ることとします．この図の構文は，「条件 1 の場合」を選択した場合はラベル LABEL1 へジャンプし，「条件 2 の場合」を選択した場合はラベル LABEL2 へジャン
プすることを表しています．

図 9.3: @@select の構造条件の数には特に決まりはありません．条件分岐数が 2 つの場合もありますし，3 つの場合も考えられます．よって，@@select 構文にはいくつの条件文が与えられたかも数える必要があります．
では，@@select 構文の実装例をお見せします．今回はユーザが選択した回答に対応したラベル名を抜き出す所までを実装しています．また，条件分岐数は 10 個以上は無いという前提のもとプログラムを作成しています．

131

9.6 条件分岐構文の作成

図 9.4: 条件分岐構文の作成

132

第 9 章 ノベルゲーム用スクリプト言語の作成
リスト 9.6: ”script-06.cpp”
1
2
3# include <stdio.h># include <string .h># include <stdlib .h>

4
5
6
7
8

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

9
10
11
12
13
14
15

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

16
17
18
19
20
21
22
23

// プロトタイプ宣言
int loadScript (const char* filename , ScriptInformation * scriptInfo );void splitString (const char* src , char* dest [], const char* delim , int splitNum );void printElements (char* elem []);
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo );int searchScriptLabel (const char* label , ScriptInformation * scriptInfo );

24
25
26
27
28
29
30

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

31
32
33
34
35
36
37
38
39
40

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (const char* src , char* dest [], const char* delim , int splitNum ){// 省略（前回と同じ）
}

41
42
43
44
45
46
47

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){// 省略（前回と同じ）
}

48
49
50
51
52
53
54
55
56
57
58

// スクリプト文を解読する// 戻り値 1: 成 功 0: 失 敗
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo ){int i, selectNum , choice ;// 分割されたスクリプト文
char* message [100];// 条件分岐用
char* selectMessage [10];char* select [10][2];

59
60
61
62

// 文字列分割時の区切り文字
const char* delim = " ";const char* selectDelim = "@@";

63
64// スクリプト分割

133

9.6 条件分岐構文の作成
splitString ( scriptMessage , message , delim , 100 );65
66// 分割に失敗した場合
if( message [0] == NULL ) {return 0;
}

67
68
69
70
71

// s c r i p t の 仕 様//// @@message 文 字 列// --- 文 字 列 を メ ッ セ ー ジ と し て 表 示 す る// @@select 条 件 1 の 場 合 @@LABEL1 条 件 2 の 場 合 @@LABEL2 条 件 3 の 場 合 @@LABEL3// --- 条 件 分 岐

72
73
74
75
76
77
78

// message [0] が @@message の 時 は ， メ ッ セ ー ジ 命 令 が 来 た と 判 断if( strncmp ( message [0], " @@message ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" メッセージ : %s\n", message [1] );return 1;}else if( strncmp ( message [0], " @@drawgraph ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" 画像 %s 表 示 -- x 座 標 : %d, y 座 標 : %d\n",message [3] , atoi( message [1] ), atoi( message [2] ) );return 1;}else if( strncmp ( message [0], " @@select ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {

79
80
81
82
83
84
85
86
87
88

for(i = 0; message [i + 1] != NULL; i++ ) {// 条件を条件文章とジャンプラベルとに分ける
splitString ( message [i + 1], selectMessage , selectDelim , 2 );// 条件文章
select [i][0] = selectMessage [0];// ラベルselect [i][1] = selectMessage [1];}// 分岐数
selectNum = i;

89
90
91
92
93
94
95
96
97
98
99

// ここで分岐を画面に表示
for(i = 0; i < selectNum ; i++) {printf (" 条件 %d : %s\n", i + 1, select [i][0] );}// 分岐を選択
choice = 0;
while( choice <= 0 || choice > selectNum ) {printf (" 選択 :");
scanf("%d", & choice );
}

100
101
102
103
104
105
106
107
108
109
110

// ここで条件へジャンプする処理を追加
printf ("%s へ ジ ャ ン プ し ま す \ n", select [ choice - 1][1] );

111
112
113

return 1;

114

}

115
116

printf (" スクリプトエラー \n");return 0;

117
118
119

}

120
121
122
123
124
125
126

// 指定したラベルがある行数を探す// 戻り値 正 の 数 : 指 定 し た ラ ベ ル の 行 番 号 -1: エ ラ ー
int searchScriptLabel (const char* label , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

127
128
129

int main (){134

第 9 章 ノベルゲーム用スクリプト言語の作成
int i;
int line;
ScriptInformation script ;

130
131
132
133

loadScript ( "./ script .txt", & script );

134
135

for( i = 0; i < script . maxLineNumber ; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );
}

136
137
138
139

for( i = 0; decodeScript ( script . script [i], & script ) != 0 ; i++ )
;

140
141
142

return 0;

143

}

144

実行結果は以下のようになりました．今回は例として「条件 2 の場合」を選択してみました．なお，今回使用した script.txt は以下のようになっています．
1@@message こんにちは，文章表示のテストです2
3@@select 条件 1 の場合@@LABEL1 条件 2 の場合@@LABEL2 条件 3 の場合@@LABEL3

4
5@@label LABEL1

6
7
8@@message 条件 1 が選択されました@@goto NEXT

9
10@@label LABEL2

11
12
13

@@message 条件 2 が選択されました@@goto NEXT

14
15@@label LABEL3

16
17
18

@@message 条件 3 が選択されました@@goto NEXT

19
20@@label NEXT

21
22@@message これで終了します1359.7 指定したラベルへのジャンプ
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです
2 : @@select 条 件 1 の 場 合@@LABEL1 条 件 2 の 場 合@@LABEL2 条 件 3 の 場 合@@LABEL3
3 : @@label LABEL1
4 : @@message 条件 1 が選択されました
5 : @@goto NEXT
6 : @@label LABEL2
7 : @@message 条件 2 が選択されました
8 : @@goto NEXT
9 : @@label LABEL3
10 : @@message 条件 3 が選択されました
11 : @@goto NEXT
12 : @@label NEXT
13 : @@message これで終了します
メッセージ : こんにちは，文章表示のテストです
条件 1 : 条件 1 の場合
条件 2 : 条件 2 の場合
条件 3 : 条件 3 の場合
選択 :2
LABEL2 へジャンプしますスクリプトエラーµ

9.7´

指定したラベルへのジャンプ指定したラベルへジャンプするプログラムを作成していきます．これは，@@goto 命令，@@select 命令どちらにも利用することができるでしょう．
指定したラベルへ移動するのは意外と簡単です．まずは，指定したラベルが何行目にあるかを取得します．これは，§ラベルの検索で既に作成しましたね．そして，ScriptInformation
構造体内にある現在読み取り中の行を表す currentLine を指定したラベルが存在する行に置き換えればよいだけです．
今回は指定したラベルへジャンプする命令@@goto 命令を作成することとします．@@goto
命令の構造は図 9.5 となっています．
では作成したプログラムをお見せします．今回も書き換えたのは decodeScript 関数の一部分だけです．
リスト 9.7: ”script-07.cpp”
1
2
3# include <stdio .h># include <string .h># include <stdlib .h>4136

第 9 章 ノベルゲーム用スクリプト言語の作成

図 9.5: @@goto の構造

5
6
7
8

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

9
10
11
12
13
14
15

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

16
17
18
19
20
21
22
23

// プロトタイプ宣言
int loadScript (const char* filename , ScriptInformation * scriptInfo );void splitString (const char* src , char* dest [], const char* delim , int splitNum );void printElements (char* elem []);
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo );int searchScriptLabel (const char* label , ScriptInformation * scriptInfo );

24
25
26
27
28
29
30

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

31
32
33
34
35
36
37
38
39
40

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (const char* src , char* dest [], const char* delim , int splitNum ){// 省略（前回と同じ）
}

41
42
43
44
45
46
47

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){// 省略（前回と同じ）
}

48
49
50
51
52
53
54
55
56
57
58

// スクリプト文を解読する// 戻り値 1: 成 功 0: 失 敗
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo ){int i, selectNum , choice , line;// 分割されたスクリプト文
char* message [100];// 条件分岐用
char* selectMessage [10];char* select [10][2];1379.7 指定したラベルへのジャンプ
59
60
61
62

// 文字列分割時の区切り文字
const char* delim = " ";const char* selectDelim = "@@";

63
64
65

// スクリプト分割
splitString ( scriptMessage , message , delim , 100 );

66
67
68
69
70

// 分割に失敗した場合
if( message [0] == NULL ) {return 0;
}

71
72
73
74
75
76
77

// s c r i p t の 仕 様//// @@message 文 字 列// --- 文 字 列 を メ ッ セ ー ジ と し て 表 示 す る// @@select 条 件 1 の 場 合 @@LABEL1 条 件 2 の 場 合 @@LABEL2 条 件 3 の 場 合 @@LABEL3// --- 条 件 分 岐

78
79
80
81

// message [0] が @@message の 時 は ， メ ッ セ ー ジ 命 令 が 来 た と 判 断if( strncmp ( message [0], " @@message ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" メッセージ : %s\n", message [1] );

82
83
84
85

}else if( strncmp ( message [0], " @@drawgraph ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" 画像 %s 表 示 -- x 座 標 : %d, y 座 標 : %d\n",message [3] , atoi( message [1] ), atoi( message [2] ) );86
87

}else if( strncmp ( message [0], " @@select ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {

88
89
90
91
92
93
94
95
96
97
98

for(i = 0; message [i + 1] != NULL; i++ ) {// 条件を条件文章とジャンプラベルとに分ける
splitString ( message [i + 1], selectMessage , selectDelim , 2 );// 条件文章
select [i][0] = selectMessage [0];// ラベルselect [i][1] = selectMessage [1];}// 分岐数
selectNum = i;

99
100
101
102
103
104
105
106
107
108
109

// ここで分岐を画面に表示
for(i = 0; i < selectNum ; i++) {printf (" 条件 %d : %s\n", i + 1, select [i][0] );}// 分岐を選択
choice = 0;
while( choice <= 0 || choice > selectNum ) {printf (" 選択 :");
scanf("%d", & choice );
}

110
111
112

// ここで条件へジャンプする処理を追加
printf ("%s へ ジ ャ ン プ し ま す \ n", select [ choice - 1][1] );

113
114
115
116
117
118
119
120
121
122
123

}else if( strncmp ( message [0], " @@goto ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// ラベルが何行目にあるかを取得
line = searchScriptLabel ( message [1], scriptInfo );// 指定したラベルが見つからなかった
if( line == -1 ) {printf (" スクリプトエラー : 指 定 し た ラ ベ ル が 見 つ か り ま せ ん で し た (% d 行 目 )\n",scriptInfo -> currentLine );return 0;}// 読み取り中の行番号をラベルの行に移動

138

第 9 章 ノベルゲーム用スクリプト言語の作成
scriptInfo -> currentLine = line;

124
125

}else if( strncmp ( message [0], " @@label ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// ラベルの場合はなにもしない}else {
printf (" スクリプトエラー (% d 行 目 )\n", scriptInfo -> currentLine );return 0;}
return 1;

126
127
128
129
130
131
132

}

133
134

// 指定したラベルがある行数を探す// 戻り値 正 の 数 : 指 定 し た ラ ベ ル の 行 番 号 -1: エ ラ ー
int searchScriptLabel (const char* label , ScriptInformation * scriptInfo ){// 省略（前回と同じ）
}

135
136
137
138
139
140
141

int main (){int i;
int line;
ScriptInformation script ;

142
143
144
145
146
147

loadScript ( "./ script .txt", & script );

148
149

for( i = 0; i < script . maxLineNumber ; i++ ) {printf ("%d : %s\n", i + 1, script . script [i] );
}

150
151
152
153

for( ; decodeScript ( script . script [ script . currentLine ], & script ) != 0 ;script . currentLine ++ )
;

154
155
156
157

return 0;

158

}

159

実行結果は以下のようになりました．なお，今回使用した script.txt は以下のようになっています．
1@@message こんにちは，文章表示のテストです2
3@@goto LABEL1

4
5@@message この行は表示されないはず6
7@@label LABEL1

8
9@@message これで終了します1399.8 ノベルゲーム用スクリプト言語解析プログラム
¶
実行結果

³

1 : @@message こんにちは，文章表示のテストです
2 : @@goto LABEL1
3 : @@message この行は表示されないはず
4 : @@label LABEL1
5 : @@message これで終了します
メッセージ : こんにちは，文章表示のテストですメッセージ : これで終了します
µ

9.8

´

ノベルゲーム用スクリプト言語解析プログラム

ようやくノベルゲーム用のスクリプト言語の完成です．今回は，@@select 構文にラベ
ルジャンプ機能を付けてみます．
では完成したプログラムをお見せします．今回はコードを省略せずにすべて書いています．
リスト 9.8: ”script-08.cpp”
1
2
3# include <stdio .h># include <string .h># include <stdlib .h>

4
5
6
7
8

// スクリプトは最大 1000 行 ま で 読 み 込 む# define SCRIPT_MAX_LINE 1000// スクリプト最大文字数# define SCRIPT_MAX_STRING_LENGTH 300

9
10
11
12
13
14
15

typedef struct ScriptInformation_tag {int maxLineNumber ;// スクリプト行数
int currentLine ;// 現在何行目を実行しているか
const char* filename ;// ファイル名
char script [ SCRIPT_MAX_LINE ][ SCRIPT_MAX_STRING_LENGTH ];} ScriptInformation ;

16
17
18
19
20
21
22
23

// プロトタイプ宣言
int loadScript (const char* filename , ScriptInformation * scriptInfo );void splitString (const char* src , char* dest [], const char* delim , int splitNum );void printElements (char* elem []);
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo );int searchScriptLabel (const char* label , ScriptInformation * scriptInfo );

24
25
26
27
28
29
30
31
32

// スクリプトファイルを読み込む// 戻り値 -1 : 失 敗 0 : 成 功
int loadScript (const char* filename , ScriptInformation * scriptInfo ){int pos;char c;// スクリプトファイル
FILE* fp;

33
34
35

// スクリプト情報を初期化
memset ( scriptInfo , 0, sizeof ( ScriptInformation ) );36
37// スクリプトファイルを開く

140

第 9 章 ノベルゲーム用スクリプト言語の作成
fp = fopen(filename , "r");if( fp == NULL ) {// ファイル読み込みに失敗
printf (" スクリプト %s を 読 み 込 め ま せ ん で し た \ n", filename );return -1;
}

38
39
40
41
42
43
44

// s c r i p t 書 き 込 み 時 に 使 用
pos = 0;45
46
47for( ;; ) {// 一文字読み込み
c = fgetc( fp );// ファイルの終わりかどうか
if( feof( fp ) ) {break;}// 文章先頭の空白部分を読み飛ばす
while( (c == ’ ’ || c == ’\t’) && pos == 0 && !feof( fp ) ) {c = fgetc ( fp );
}

48
49
50
51
52
53
54
55
56
57
58
59

if( pos >= SCRIPT_MAX_STRING_LENGTH - 1 ) {//1 行 の 文 字 数 が 多 す ぎ る
printf ("error: 文 字 数 が 多 す ぎ ま す (% d 行 目 )", scriptInfo -> currentLine );return -1;
}

60
61
62
63
64
65

// 改行文字が出てきた場合，次の行へ移動
if( c == ’\n’ ) {// 空行は読み飛ばす
if( pos == 0 ) {continue ;}//\0 を 文 字 列 の 最 後 に 付 け る
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = ’\0 ’;// 次の行に移動
scriptInfo -> currentLine ++;// 書き込み位置を 0 に す る
pos = 0;}else {// 書き込み
scriptInfo -> script [ scriptInfo -> currentLine ][ pos ] = c;// 文字書き込み位置をずらす
pos ++;
}

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

}
// 最大行数
scriptInfo -> maxLineNumber = scriptInfo -> currentLine ;// 読み込み中の行を 0 に す る
scriptInfo -> currentLine = 0;// スクリプトファイル名を設定
scriptInfo -> filename = filename ;

84
85
86
87
88
89
90
91

return 0;

92
93

}

94
95
96
97
98
99
100
101
102

// 文字列分割 (1 行 の 最 大 文 字 数 は SCRIPT_MAX_STRING_LENGTH )// src : 分 割 し た い 文 字 列// dest: 分 割 さ れ た 文 字 列// delim: 区 切 り 文 字// splitNum : 最 大 分 割 数
void splitString (const char* src , char* dest [], const char* delim , int splitNum ){int i;

1419.8 ノベルゲーム用スクリプト言語解析プログラム
char* cp;
char* copySrc ;

103
104
105

// 元の文章をコピーする
copySrc = (char *) malloc ( sizeof (int) * SCRIPT_MAX_STRING_LENGTH + 1);
strncpy ( copySrc , src , SCRIPT_MAX_STRING_LENGTH );cp = copySrc ;

106
107
108
109
110

// s t r t o k を 使 っ て copySrc を d e l i m 区 切 り で 分 割 す る
for( i = 0; i < splitNum ; i++ ) {// 分割対象文字列が無くなるまで分割
if( (dest[i] = strtok (cp , delim )) == NULL ) {break;}//2 回 目 に s t r t o k を 呼 び 出 す 時 は ， c p を N U L L に す る
cp = NULL;}// 分割された文字列の最後の要素は N U L L と し て お く
dest[i] = NULL;

111
112
113
114
115
116
117
118
119
120
121
122

}

123
124
125
126
127
128
129
130
131
132

// デバッグ用// e l e m の 要 素 を 表 示
void printElements (char* elem []){int i;
for( i = 0; elem[i] != NULL; i++ ) {printf ("%d : %s\n", i + 1, elem[i] );
}
}

133
134
135
136
137
138
139
140
141
142
143

// スクリプト文を解読する// 戻り値 1: 成 功 0: 失 敗
int decodeScript (const char* scriptMessage , ScriptInformation * scriptInfo ){int i, selectNum , choice , line;// 分割されたスクリプト文
char* message [100];// 条件分岐用
char* selectMessage [10];char* select [10][2];

144
145
146
147

// 文字列分割時の区切り文字
const char* delim = " ";const char* selectDelim = "@@";

148
149
150

// スクリプト分割
splitString ( scriptMessage , message , delim , 100 );

151
152
153
154
155

// 分割に失敗した場合
if( message [0] == NULL ) {return 0;
}

156
157
158
159
160
161
162

// s c r i p t の 仕 様//// @@message 文 字 列// --- 文 字 列 を メ ッ セ ー ジ と し て 表 示 す る// @@select 条 件 1 の 場 合 @@LABEL1 条 件 2 の 場 合 @@LABEL2 条 件 3 の 場 合 @@LABEL3// --- 条 件 分 岐

163
164
165
166

// message [0] が @@message の 時 は ， メ ッ セ ー ジ 命 令 が 来 た と 判 断if( strncmp ( message [0], " @@message ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" メッセージ : %s\n", message [1] );

167

142

第 9 章 ノベルゲーム用スクリプト言語の作成}else if( strncmp ( message [0], " @@drawgraph ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {printf (" 画像 %s 表 示 -- x 座 標 : %d, y 座 標 : %d\n",message [3] , atoi( message [1] ), atoi( message [2] ) );

168
169
170
171

}else if( strncmp ( message [0], " @@select ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {

172
173

for(i = 0; message [i + 1] != NULL; i++ ) {// 条件を条件文章とジャンプラベルとに分ける
splitString ( message [i + 1], selectMessage , selectDelim , 2 );// 条件文章
select [i][0] = selectMessage [0];// ラベルselect [i][1] = selectMessage [1];}// 分岐数
selectNum = i;

174
175
176
177
178
179
180
181
182
183
184

// ここで分岐を画面に表示
for(i = 0; i < selectNum ; i++) {printf (" 条件 %d : %s\n", i + 1, select [i][0] );}// 分岐を選択
choice = 0;
while( choice <= 0 || choice > selectNum ) {printf (" 選択 :");
scanf ("%d", & choice );
}

185
186
187
188
189
190
191
192
193
194
195

// ラベルが何行目にあるかを取得
line = searchScriptLabel ( select [ choice - 1][1] , scriptInfo );// 指定したラベルが見つからなかった
if( line == -1 ) {printf (" スクリプトエラー : 条 件 分 岐 の 指 定 ラ ベ ル が 間 違 っ て い ま す (% d 行 目 )\n",scriptInfo -> currentLine + 1 );return 0;}// 読み取り中の行番号をラベルの行に移動
scriptInfo -> currentLine = line;

196
197
198
199
200
201
202
203
204
205
206

}else if( strncmp ( message [0], " @@goto ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// ラベルが何行目にあるかを取得
line = searchScriptLabel ( message [1], scriptInfo );// 指定したラベルが見つからなかった
if( line == -1 ) {printf (" スクリプトエラー : 指 定 し た ラ ベ ル が 見 つ か り ま せ ん で し た (% d 行 目 )\n",scriptInfo -> currentLine + 1);
return 0;}// 読み取り中の行番号をラベルの行に移動
scriptInfo -> currentLine = line;

207
208
209
210
211
212
213
214
215
216
217
218

}else if( strncmp ( message [0], " @@label ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// ラベルの場合はなにもしない}else {
printf (" スクリプトエラー (% d 行 目 )\n", scriptInfo -> currentLine + 1);
return 0;}
return 1;

219
220
221
222
223
224
225
226

}

227
228
229
230
231
232

// 指定したラベルがある行数を探す// 戻り値 正 の 数 : 指 定 し た ラ ベ ル の 行 番 号 -1: エ ラ ー
int searchScriptLabel (const char* label , ScriptInformation * scriptInfo ){// 分割されたスクリプト文143

9.8 ノベルゲーム用スクリプト言語解析プログラム
char* message [100];// 文字列分割時の区切り文字
const char* delim = " ";int i, line = -1;

233
234
235
236
237

for( i = 0; i < scriptInfo -> maxLineNumber ; i++ ) {// スクリプト分割
splitString ( scriptInfo -> script [i] , message , delim , 100 );

238
239
240
241

// 分割に失敗した場合
if( message [0] == NULL ) {return -1;
}

242
243
244
245
246

// 指定したラベルを探す
if( strncmp ( message [0], " @@label ", SCRIPT_MAX_STRING_LENGTH ) == 0 ) {if( strncmp ( message [1], label , SCRIPT_MAX_STRING_LENGTH ) == 0 ) {// 指定したラベルが見つかった時
line = i;break;
}
}

247
248
249
250
251
252
253
254

}

255
256

return line;

257

}

258
259

int main (){int i;
int line;
ScriptInformation script ;

260
261
262
263
264
265

loadScript ( "./ script .txt", & script );

266
267

printf ("\ n ス ク リ プ ト 開 始 \n\n");

268
269

for( ; decodeScript ( script . script [ script . currentLine ], & script ) != 0 ;script . currentLine ++ )
;

270
271
272
273

return 0;

274

}

275

実行結果は以下のようになります．なお，今回使用した script.txt は以下のようになって
います．
1
2

@@message こんにちは@@message ノベルゲーム用スクリプトエンジンのテストです3
4@@message まずは条件分岐です5
6@@message あなたの年齢を教えてください78@@select 20 歳未満@@UNDER 20 歳以上@@UPPER

9
10
11

@@label UNDER@@message あなたは 20 歳未満なのですね!
144

第 9 章 ノベルゲーム用スクリプト言語の作成
12@@goto NEXT

13
14
15
16

@@label UPPER@@message あなたは 20 歳以上なのですね!
@@goto NEXT

17
18@@label NEXT

19
20@@message 画像 human.png を表示しますか21
22@@select はい@@YES いいえ@@NO

23
24
25
26
27

@@label YES@@message といっても本当に画像が表示されるわけではないのですが・・・@@drawgraph 50 100 human.png@@goto NEXT2

28
29
30

@@label NO@@goto NEXT2

31
32@@label NEXT2

33
34
35
36
37

@@message まだまだこのスクリプトエンジンは改良の余地があります@@message 例えば，フラグの値によって条件を分岐したり，などです@@message その辺りは読者の方がご自身で実装してみてください@@message がんばればできます1459.9 ノベルゲーム用スクリプト言語解析プログラムの改良
¶
実行結果

³

スクリプト開始
メッセージ : こんにちは
メッセージ : ノベルゲーム用スクリプトエンジンのテストですメッセージ : まずは条件分岐です
メッセージ : あなたの年齢を教えてください
条件 1 : 20 歳未満
条件 2 : 20 歳以上
選択 :1
メッセージ : あなたは 20 歳未満なのですね!
メッセージ : 画像 human.png を表示しますか
条件 1 : はい条件 2 : いいえ
選択 :1
メッセージ : といっても本当に画像が表示されるわけではないのですが・・・
画像 human.png 表示 -- x 座標 : 50, y 座標 : 100
メッセージ : まだまだこのスクリプトエンジンは改良の余地があります
メッセージ : 例えば，フラグの値によって条件を分岐したり，などですメッセージ : その辺りは読者の方がご自身で実装してみてください
メッセージ : がんばればできます
µ

9.9

´

ノベルゲーム用スクリプト言語解析プログラムの改良

146