DXライブラリを使ったシューティングゲームの作成
理工学部 数理情報学科 T060061 田平誠憲 指導教員 池田勉概要
今の社会は、大人から子供まで多くの人がゲームをやっており、今や誰でもゲームをやっている 世の中である。私も小さい頃から任天堂で発売されたファミリーコンピューターでよく遊んでお り、今でも毎日のようにニンテンドーDSや Wii などの機種に触れている。しかし、最近のTV ゲームの発展はすさまじく、操作方法や画面が見づらいということもあり、TVゲーム初心者がな かなかゲームに馴染みづらい環境にある。そこで、もっと多くの人にゲームを楽しんでもらうた め、シンプルでかつ誰でも楽しめるゲームを作ろうと思ったことがきっかけで、自分自身で1から ゲームを作ってみようと思った。 ゲームを作る手順として、ゲームを作る環境を整えなければならない。プログラムを作る際に 使用したのはマイクロソフト社の Visual C++ 2008 Express Edition である。Visual C++ 2008Express Editionはマイクロソフトのホームページからダウンロードできるようになっており、自 宅のパソコンの環境でも無料でプログラミングができるところが魅力的だ。そして、ゲームを作る ソフトはDXライブラリを使用した。DXライブラリはプログラミングが苦手な人でも、C++の 基本的な知識があればゲームを作成ことができるので、プログラミングが苦手な私でもゲームが作 れると思い、DXライブラリを使用した。 私はプログラミングが苦手で、初めはC++の復習から始め、その後にDXライブラリを扱って いるホームページからサンプルプログラムなどを動かすなりしてゲームプログラミングの基礎を学 び、それから実際に1からシューティングゲームを作成した。ゲームの内容は初心者から上級者ま であそべるように6段階の難易度と時間、キャラクターを自分に合ったものを選択し、ゲームがス タートする。敵を倒したりすることでスコアが伸び、制限時間内にスコアが取れるかという内容で ある。また、ゲーム内のキャラクターに、敵や敵の発射する玉に当たると体力が減り、体力が0以 下になればゲームオーバーとなり、制限時間内に体力が残っていればクリアとなる。中には、食べ ものを取ると体力増加というようにゲームに有利なものもあるようになっている。その他にも技を 選択することにより、様々な攻撃ができるようにしている。このプログラムを組むにあたって、プ ログラムの中身や見た目を良くすることを心掛け、画像も Paint.net というソフトを使ってできる だけ見た目をきれいにすることを心掛けた。しかし、C++の復習や就職活動に多くの時間を使っ てしまい、実際にゲームを作成したのが7月中旬だったので、ゲームの内容や動きが思ったほどう まく仕上げられなかったことが残念である。しかし、ゲームを作る内に、ゲームを作ることが楽し くなり、今後はもっと納得のいくゲームやアプリケーションを作っていきたいと思います。
2009
年度 卒業論文
DXライブラリを使った
シューティングゲームの作成
龍谷大学 理工学部 数理情報学科
T060061
田平 誠憲
指導教員 池田 勉
目 次
1 はじめに 4
2 開発環境 4
2.1 Visual C++ 2008 Express Edition . . . . 4
2.2 DXライブラリ . . . . 4 2.3 ゲーム作成のまでの手順 . . . . 5 3 DXライブラリのコマンド 6 3.1 キーによる入力 . . . . 6 3.2 文字などに色をつける . . . . 7 3.3 文字の表示 . . . . 7 3.4 絵の表示 . . . . 8 4 スタート画面について 10 4.1 タイトル画面の表示 . . . . 10 4.2 カーソルを動かす . . . . 12 4.3 制限時間の設定 . . . . 12 4.4 難易度の設定 . . . . 13 4.5 キャラクターの選択と能力 . . . . 14 5 玉を発射するプログラム 16 6 データを表示するプログラム 18 6.1 HPバーの表示 . . . . 18 6.2 技の選択画面 . . . . 20 7 まとめ 21 8 参考文献 22 9 プログラム 23
1
はじめに
私は小さい頃からゲームが好きで、これまでファミリーコンピュータをはじめ、ニンテンドウ6 4,PS2,ニンテンドーDSなど多くの機種に触れてきた。そんな私は、大学で学んだC言語の 知識でゲームが作れないのかと思い、自分自身でゲームを作ってみようと思ったのがこの研究を始 めたきっかけである。使用した言語はC++で、ゲーム作成のホームページを参考にしながら簡単 なアニメーションを作ったり、いろんなサンプルプログラムを動かしたりして、ゲーム作成の基礎 を学んだ。サンプルプログラムの一つに玉を発射するプログラムがあり、それを改良しているうち にシューティングゲームを作ってみようと思い、シューティングゲームの作成を始めた。2
開発環境
ゲームを作るためには、ある程度環境を整えなければならない。そのために、私はプログラムを 作る環境にするため、マイクロソフト社が開発した「Visual C++ 2008 Express Edition」を使用 することにした。Visual C++ 2008 Express Edition のメリットはなによりも無料で手に入るとい うことだ。それに、多くの企業がシステム開発に Visual C++ 2008 Express Edition を用いてお り、初心者から上級者までが扱えるソフトである。 次に、ゲームを作るソフトをダウンロードしなければならない。私はDXライブラリを使って ゲームを開発した。DXライブラリとは、DirectX というゲーム開発ソフトを使った Windows ソ フトの開発に必ず付いている DirectX や Windows 関連のプログラムを使い易くまとめた形で利用 できるようにしたC++言語用のゲーム開発ソフトである。そのため、ゲーム開発にはC言語の知 識だけでよく、無料で手に入るので私はこれに決めた。DXライブラリで作成できるゲームの範囲 は、店で市販されている本格的な3Dゲームから簡単なゲーム作成まで幅広くでき、初心者にも簡 単にゲーム作成ができる。また、ゲームだけでなくさまざまなアニメーションやチャットやペイン トソフトなどのアプリケーションを作成できることもDXライブラリの魅力である。2.1
Visual C++ 2008 Express Edition
Visual C++ 2008 Express Editionはマイクロソフトのホームページで入手できる。Visual C++
2008 Express Editionはゲーム開発他にも、授業の予習復習に使うプログラムの作成もできるため、
多くの学生が使用している。ダウンロードの仕方としてはまず、「Microsoft Visual Studio Express
Edition」にアクセスする。その中で「Visual C++ 2008」のところのある「はじめての方のため のインストール方法」を見ながら「Web インストール (ダウンロード)」をクリックする。ここで はダウンロードのやり方が大変丁寧に説明してあるので、そうすれば Visual C++ 2008 Express Editionのインストールが始まる。インストールは非常に時間がかかるため、覚悟しておいた方が よい。
2.2
DXライブラリ
DXライブラリは「DXライブラリのホームページ」もしくは「C言語何でも質問サイト」の 「ゲームプログラミングの館」に行けばダウンロードできる。また、ゲームプログラミングの館の ページでも Visual C++ 2008 Express Edition をダウンロードできるので、ここからダウンロードしてもよい。「龍神録プログラミングの館」ではもっとハードで複雑なゲームの開発ができるが、 ここではオリジナルのゲームができないと思ったことと、基礎からゲームを作成したかったので 「ゲームプログラミングの館」で基礎を学ぶと同時にそれを自分なりに応用したプログラムを作り
上げた。
2.3
ゲーム作成のまでの手順
まず、Visual C++ 2008 Express Edition を開き、「ファイル(F)」から「新規作成(N)」を選
び「プロジェクト(P)」を選択する。そしたら図 1 のような画面が出るので、画像のように「プ ロジェクトの種類(P)」を「Win32」に設定し、「Win32 プロジェクト」選択する。そして、「プ ロジェクト名(N)」に名前を入力し、「ソリューションのディレクトリを作成(D)」にチェック を入れ、「OK」をクリックする。次に図 2 のような画面が表示されるので、「次へ」をクリックす る。その次は「空のオブジェクト」にチェックを入れ、「完了」をクリックする。するとスタート 画面に戻るので、今度はツールバーの「プロジェクト(P)」から「新しい項目の追加(W)」をク リックし、「テンプレート」の「C++ファイル(cpp)」を選択し、名前を入力する。このような設 定をやってあげることでプログラムが書けるようになり、授業で使うようなプログラムを書くなら これでよいが、DXライブラリでゲームを作成するならもう少し設定しなければならない。 ゲームを作るためには、ツールバーにある「プロジェクト(P)」から「(ファイル名)のプロパ ティ(P)」をクリックする。そしたら、構成プロパティをクリックする。そしたら、全般の文字 セットを「マルチ バイト文字セットを使用する」にしておく(図 3)。次に C/C++のコード生成 にあるランタイムライブラリをマルチスレッド デバッグ (/MTd) に設定する。最後に左上の構成 (C)を図 4 のように「アクティブ(Debug)」から「Release」に変更し、ランタイムライブラリ を「マルチスレッド (/MT)」に設定し、OKをクリックする。このような設定をやればゲームが 作成できる。 図 1: 新しいプロジェクト 図 2: Win32 アプリケーションウィザード 図 3: マルチ文字バイトの設定 図 4: Release の設定
3
DXライブラリのコマンド
基本的にDXライブラリでゲームを作る為に必要な知識は if 文、for 文程度ができればいいが、 普通に C 言語でプログラムを作成するときとDXライブラリでゲームを作るのでは、一部コマン ドが違うことがある。例えば、文字を画面上に表示するときは文字の場所や大きさを指定しなけれ ばならない。ここでは、ゲーム作成に必要なものをいくつか紹介する。 #include ”DxLib.h” #include <math.h>int WINAPI WinMain(){
} このプログラムがDXライブラリの基本的な書き方である。 ¨ § ¥ ¦ #include”DxLib.h” C言語でいう ”#include<stdio.h>” の部分であり、このプログラムでは DX ライブラリを使用す
るということである。同様に”int WINAPI WinMain()” は ”int main(void)” の部分に当てはまる。 ¨ § ¥ ¦ #include <math.h> C言語同様に三角関数などの数学公式を使うときに用いられる。ゲームを作る上で数学や物理の知 識は必要で、私の作ったゲームでは玉を斜めに飛ばす場面で 1sin、cos といった三角関数を使って いる。 ¨ § ¥ ¦ ChangeWindowMode(); 画面をウィンドウモードかフルスクリーンモードかを設定するものである。ウィンドウモードと はパソコンの画面に小さく表示されるため、ゲームをするためには最適な画面といえる。ウィン ドウモードにするには () の中に TRUE を書けばよい。対照的にフルスクリーンモードとはパソコ ンの画面全体にゲームが表示される。フルスクリーンモードにするには () の中に FALSE を書く か、 ”ChangeWindowMode();” 自体書かないのと2通りのやり方がある。しかし、フルスクリー ンモードでは画面が見づらく実行するときの読み込みが若干長いため、できるだけウィンドウモー ドで実行した方がよい。
3.1
キーによる入力
一般的に市販されているゲームではボタンを押して操作するものが大半であるが、ここでは入力 した文字を読み込むプログラムを紹介する。 º ¹ · ¸ char Key[256]; GetHitKeyStateAll(Key);これは、キーボードに入力したキーをプログラムに読み込むものである。例えば、キーボードから Zキーを入力したときは、Z キーが押したとプログラムが認識しゲーム画面でも Z キーを押したこ とにより何らかの操作ができるようになる。もし、これを書かなければプログラムが入力したキー を読み込まないので、何かを入力しても全く反応しないようになる。また、Z キーを押したときに 何かの操作を行いたいときは ¨ § ¥ ¦ if(Key[ KEY INPUT Z]==1)
と書けばよい。こうすれば、Zキーを入力したときに何らかのアクションを起こしたりする。
3.2
文字などに色をつける
ゲームによってはキャラクターごとにセリフの文字の色が違っていたりするときがある。ここで 紹介するのは、画面上に表示する文字や図形に色をつけるプログラムを紹介する。 ¨ § ¥ ¦ GetColor(255,255,255) 括弧内の数字は色の濃さであり左から順に赤、緑、青色の濃さであり、括弧内の数字を変えること で色を変更できる。この数字は 0 から 255 までの数字を書き込め、数字が 0 に近ければ色が濃くな り、数字が大きければ色が薄くなるということである。例えばGetColor(255,255,255)・・・白 GetColor(255,0,0)・・・赤 GetColor(0,0,128)・・・濃い青 GetColor(255,255,0)・・・黄色 GetColor(0,0,0)・・・黒 というように書くことで、文字の色が変更される。このことから、DX ライブラリで表示できる色 は 224色、つまり 1600 万色以上もの色が使えるということだ。
3.3
文字の表示
普段使用しているC言語とは違い、ゲームでは文字が表示される位置も指定しなければならな い。ここではC言語でいえば”printf(””);”と同じ、文字を表示するプログラムを紹介する。 ² ± ¯ ° (1)DrawString(100, 100, ”シューティングゲーム” ,GetColor(255,255,255)); (2)DrawFormatString(100 ,150 ,GetColor(255,0,0) ,” Sum=% d”,Sum);DXライブラリは画面左上の座標が (0,0) で右下の座標が (640,480) で表わされる。これは設定す ることが可能だが、私のゲームではこのようなことは設定してないのでここでは省略する。このコ マンドは画面上に文字を表示するコマンドである。(1) の左側にある2つも数字は左から x,y 軸の 座標を示しており、右側の”GetColor(255,255,255)”は先ほど紹介した色を変えるコマンドである。 この場合は画面上の座標 (100,100) の位置に白色の文字で「シューティングゲーム」と表示される。 (2)は% d などで計算結果を表示するものである。先ほどの (1) では計算結果が表示されず、括弧 内も違ってくるので注意が必要だ。この場合なら座標 (100,150) の位置に赤色の文字で Sum の計
算結果が表示される。また、文字の大きさや筆記体を変える場合は ' & $ % int Moji;
Moji=CreateFontToHandle(”HGS創英角ポップ体”, 48, 1, DX FONTTYPE NORMAL); //・・・(1) DrawStringToHandle(100 ,100 ,”シューティングゲーム” ,GetColor(255, 255, 255) ,Moji); //・・・(2A) DrawFormatStringToHandle(100 ,150 ,GetColor(255, 0, 0) ,Moji, ”% d ” ,Sum); //・・・(2B)
というようにすればよい。まず int 型の変数を用意して、(1) のコマンドに文字の大きさや太さを 代入する。(1) は中心にある2つの文字は左から大きさ、太さであり、右の「”HGS 創英角ポップ 体”」は文字の筆記体を表している。この場合は「文字の大きさが48, 太さ1の HGS 創英角ポッ プ体」が変数 Moji に代入された。これに (2A)、(2B) のようにプログラムを書けば先ほどの文字 が「文字の大きさが48, 太さ1の HGS 創英角ポップ体」で表示される。
3.4
絵の表示
ゲームやアニメーションに必要なのは、自分が描いた絵を動かしたり、背景を表示したりするこ とである。ここでは絵を画面上に表示するプログラムを紹介する。 Â Á ¿ À int Picture1; Picture1=LoadGraph(”Pic1.png”); //・・・(1) DrawGraph(100, 100, Pic1, FALSE); //・・・(2)これは画面上に絵を表示するプログラムである。まず、int 型の変数”Picture1”と配列の変数である を用意し、(1) によってフォルダにある画像”Pic1.png”を読み込み、(2) で画面上の座標 (100,100) の位置に画像”Pic1.png”を表示させる。 また、図 5 のように多くの画像を分割して描くことのより、一度多くの画像を読み込むことが可能 である。今度は図 5 のような分割した画像を読み込み、それを分割したものを画面上に表示させる プログラムを紹介する。 Â Á ¿ À int Picture2[100]; LoadDivGraph(”Pic2.png”, 16, 4, 4, 20, 20, Picture2); //・・・(3) DrawGraph(100, 200, Picture2[0], FALSE); //・・・(4)
図 5 のような画像を4×4に分割する場合は配列で読み込む必要があるため、変数”Picture2[100]” を用意する。その次に (3) で画像”Pic2.png”を読み込むと同時に変数”Picture2[100]”に代入する。 (2)の括弧内の中身は左からフォルダ内の画像、分割する画像の数、画像の縦横の分配数、画像1つ の縦横の大きさ、変数となっている。この場合は変数”Picture2”に「フォルダ内の画像”Pic2.png” を4×4に分割し、大きさ縦20で横20の画像16個を格納した」という意味である。これを (4)のように画面上の座標 (100,200) の位置に画像”Pic2.png”の0番目の画像を表示するという意 味である。
画像の拡大や回転を同時に行うときは º ¹ · ¸ double PI=3.14;
DrawRotaGraph(100, 300, 2.0f, PI/2 ,Picture2[0] ,TRUE );
とすればよい。これは画面上の座標 (100,300) のところに、図 5 の0番目の画像を2倍して、かつ π/2 すなわち90度回転したものである。画像の拡大は括弧内の3番目の 2.0f のところを変更す ればよく、画像の回転は4番目の数字を double 型の変数で円周率 PI を用意し、回転させる角度を 決める。
4
スタート画面について
ゲームを始めると必ずといっていいほどタイトル画面が出てくる。多くのゲームはここでゲーム の設定を変更するようになっており、私が作成したゲームでもゲームを始めると図 6 のようなタイ トル画面が出て、ここでゲームの設定ができるようになっている。また、ゲームの設定内容やキャ ラクターの能力が下のコメント欄に表示するようになっている。このタイトル画面では制限時間と ゲームの難易度、キャラクターが設定でき、カーソル(黄色の四角形)を”Game Start”に合わせ SPACEキーを押すか、そのまま RETURN キーを押せばゲームスタートとなる。ここでは、タイ トル画面のプログラムの中身について説明する。 図 6: タイトル画面4.1
タイトル画面の表示
先ほどのタイトル画面は、このようなプログラムで表現している。まず while 文で無限ループを 作ることにより、何らかの操作がない限り、画面はタイトル画面のまま表示される。画面上に表示 されているカーソルを動かすことで、ゲームの設定ができるが、下記のプログラムは図 6 だけを表 示するためのもので、カーソルの操作などは後述に記してあるとおりである。 void GameStart(void) { while(1){ GetHitKeyStateAll( Key ) ; if( ProcessMessage() == -1 ) break ;DrawGraph(0, 0, Open, FALSE);
DrawStringToHandle( 100 , 80 , ”天 空 の 牧 場” , GetColor(255, 255, 0) , FontHG48);
DrawBox(195, 250+25*start select, 510, 275+25*start select, GetColor(255, 255, 0) , FALSE);
DrawStringToHandle( 250 , 250 , ”PUSH RETURN ” , GetColor(255, 255, 255) , FontHGS24); DrawGraph(200, 252, teki0[0], TRUE);
DrawFormatStringToHandle(250 , 275 , GetColor(255, 255, 255) , FontHGS24, ”Time : %d” ,time1);
DrawGraph(200, 277, Apple[0], TRUE);
DrawStringToHandle( 250 , 300 , ”難易度 : ” , GetColor(255, 255, 255) , FontHGS24); DrawFormatStringToHandle(360 , 300 ,Lv color , FontHGS24, ”%s ” ,Lv1);
DrawGraph(200, 302, Apple[13], TRUE); ¨ § ¥ ¦ while(1){ } これはC言語同様、無限にループさせるものであり、このループから抜け出すには「break;」を使 えばよい。break は括弧内の中から途中で抜け出したいときに使われており、ゲームを作る際には、 多くの場面で使用される。私のゲームではタイトル画面、ゲーム画面、ゲーム終了画面の3つの場 面に使われており、これらはゲームを始める時に RETURN キーを押したり、制限時間がなくなる ときにループから抜け出して、次の画面に移ったり、終了するようになっている。 ² ± ¯ ° if( ProcessMessage() == -1 ) break; エラーが起きた場合やゲームを終了したいときに時に、ゲームの実行を終了させたいことがある。 そのときは、これを書いてやればよい。これも同様に「break;」が書いてあり、エラーが起こった 時に、無限ループ内から抜け出してゲームを終了させるプログラムである。 しかし、これらのプログラムは無限ループ内に書かなければ操作することやエラーが発生しても 終了することができない。その理由としては、無限ループ中は while(1) 内のプログラムしか実行 しないため、たとえプログラムのどこかにこの2つのプログラムを書き込んでいても、無限ループ 内ではそれを実行しないのが原因である。ですから、無限ループを行う際は必ずこのプログラムを 書かなければならない。 ¨ § ¥ ¦ DrawBox(195, 250+25*start select, 510, 275+25*start select, GetColor(255, 255, 0), FALSE); これはゲームの難易度や時間を設定するための黄色い四角形のカーソルを画面上に表示するコマ ンドである。括弧内の「GetColor(255, 255, 0)」は四角形の色を設定しており、括弧内の初めにあ る「195, 250+25*start select 」は四角形の左上の座標、「510, 275+25*start select 」は四角形の 右上の座標を表している。この start select の値は上下キーを動かすことにより四角形の y の値を 変更することができ、それにより「Time」や「難易度」にカーソルを合わせることができる。
4.2
カーソルを動かす
これが、カーソルを動かすプログラムである。先ほど紹介した四角形はこのようなプログラムを 作ることで動かすことができる。
if(start counter<10) start counter++;
else if(Key[ KEY INPUT DOWN ] == 1 ){ start counter=0;
start select++; if(start select>3) start select=1;
}
else if(Key[ KEY INPUT UP ] == 1 ){ start counter=0; start select–; if(start select<1) start select=3; } カウンタ start counter++; を用意することで、ある一定の間隔でカーソルを動かすことができる。 このカウンタが一定までいきかつ、↓キーを入力すれば start select の値が -1 となることでカー ソルが下に動き、↑キーを入力すれば start select の値が +1 されカーソルが上に動くようになっ ている。また、↓キーを押しすぎた場合、カーソルは1番上にいくようにしており、↓キーを押し すぎた場合は1番下にくるようにしている。
4.3
制限時間の設定
このプログラムは制限時間を設定するプログラムである。先ほどと違うのはスタート画面の 「Time」に合わせてそれと同時に左右キーで時間を設定するというものだ。 if(time counter<10) time counter++;else if(start select==1 && Key[ KEY INPUT RIGHT ] == 1 ){ time counter=0;
if(time select>7) time select=7;
}
else if(start select==1 && Key[ KEY INPUT LEFT ] == 1 ){ time counter=0; time select–; if(time select<1) time select=1; } if(time select==0) time1=10; else if(time select==1) time1=30; : :
else if(time select==7) time1=600; このプログラムも、カウントを用意することで、カーソルの動きを遅めている。左右キーで time select の値を変更することで制限時間を 30 から 600 までの7段階に設定できる。
4.4
難易度の設定
最近のゲームは主にゲーム経験者を対象にしたゲームが多く発売しており、ゲームをやったこ とがない人や初心者向けのゲームが少なくなっていると私は思う。だからといって、初心者向けの げームを上級者がやってもつまらないこともある。そこで、私のゲームでは難易度をつけてやるこ とで初心者から上級者まで遊べると思い、6 段階の難易度をつけることにした。 if(Lv counter¡10) Lv counter++; : } if(Lv==1){ strcpy(Lv1,”Very Easy”); Lv color=GetColor(0, 255, 255); } if(Lv==2){ strcpy(Lv1,”Easy”); Lv color=GetColor(0, 255, 0); : }先ほどの時間設定のように、「難易度」にカーソルを合わせて、左右キーで Lv という変数、つまり 難易度を設定できる。ここまでは先ほどのプログラムと類似しているので、ここは省略する。ゲー ムの難易度は essy や hard というように画面上に出したいため ¨ § ¥ ¦ strcpy(Lv1,”very easy”); というやり方で、Lv1 という変数に「very easy」の文字を代入している。また、難易度によって 「easy」などの文字の色を変更できるようにすることにより、難易度が見やすいのではと思い Lv colorという変数を用意して、そこに色を代入することにより難易度に対応した文字の色を変更で きるようにした。上の例では Lv という変数が 2 であるなら難易度は「easy」でこの文字の色は緑 色でタイトル画面に表示されるようになる。
4.5
キャラクターの選択と能力
多くのゲームはたくさんのキャラクターを出すことによって、よりゲームの魅力や楽しさを引き 出すものである。さらにキャラクターに個性をつけるために別々の能力を設定することで戦略が増 すであろう。このゲームのキャラクターの能力はHP(体力)、パワー、スピード、カウント(連 打力)の4つの能力がありそれぞれ1から5の5段階で設定され図 7 のように星の数で表してい る。これによりキャラクターによって強かったり、使いやすかったりとキャラクターごとに個性が ある。例えば図 7 のキャラクターは「HP2、パワー2、スピード5、カウント3」という意味で あり、スピードは最高の評価の5であるが、体力とパワーがないのが欠点である。 図 7: キャラクター設定 //プロトタイプ宣言 int Maxhp0[10]={3, 2, 5, 2, ・・・}; int attack0[10]={3, 1, 5, 2, ・・・}; int speed0[10]={3, 3, 1, 5, ・・・}; int count0[10]={2, 5, 1, 3, ・・・}; これがキャラクターの能力を int 型の配列で表したやつであり、上から順にHP,パワー、スピー ド、カウントの順で並んでいる。図 7 のキャラクターはちょうど4番目に並んでいる。この変数の 値は関数 ”void GameStart(void)” 内でなくプロトタイプ宣言で宣言している。こうすることで、この値は1回読み込むだけでいいのでできるだけ、ゲームを作る際は多くの変数はプロトタイプ宣 言で宣言した方がよい。
else if(start select==3){
DrawStringToHandle(110, 375, ”HP :” , GetColor(0, 200, 255) , FontHGS20); DrawStringToHandle(110, 400, ”パワー :” , GetColor(255, 0, 50) , FontHGS20); DrawStringToHandle(110, 425, ”スピード:” , GetColor(255, 255, 0) , FontHGS20); DrawStringToHandle(110, 450, ”カウント:” , GetColor(0, 255, 0) , FontHGS20); for(i=0; i¡Maxhp0[character]; i++)
DrawStringToHandle(200+20*i, 375, ”★” , GetColor(0, 200, 255) , FontHGS20); for(j=0; j¡attack0[character]; j++)
DrawStringToHandle(200+20*j, 400, ”★” , GetColor(255, 0, 50) , FontHGS20); for(k=0; k¡speed0[character]; k++)
DrawStringToHandle(200+20*k, 425, ”★” , GetColor(255, 255, 0) , FontHGS20); for(m=0; m¡count0[character]; m++)
DrawStringToHandle(200+20*m, 450, ”★” , GetColor(0, 255, 0) , FontHGS20);
} ここではキャラクターの能力を描いているプログラムを紹介する。しかし、キャラクターを増や すと、その分そのキャラクター数だけ ”DrawStringToHandle” で1人1人の能力をプログラムが 大変見づらくなり、能力調整のときもいちいち書き直さなければならない。そこで、このように for文で書くことにより、先ほど紹介した int 型の配列に対応したキャラクターの能力を表現する星 を描けるようになる。また、int 型の変数 character はどのキャラクターを選択しているかのデー タを格納している。なお、キャラクター選択のプログラムは省略している。
5
玉を発射するプログラム
基本的にシューティングゲームは敵に玉を発射して、敵を倒すゲームである。発射する玉はある 程度時間をおいて発射するようにカウンターを用意している。こうすることで、キーを押しっぱな しでも一定の間隔で玉を発射できるようになる。ここでは自機が玉を発射するプログラムを紹介す る。
void tama Input(void)
{
if(tama counter<5) //・・・(1) tama counter++;
else if(Waza select==0 && Key[ KEY INPUT SPACE ] == 1 ){ //・・・(2) tama counter=0; shot=tamaMAX; for(j=0; j<shot; j++){ if(tama[j].flag==0){ tama[j].flag=1; tama[j].x=x+16; tama[j].y=y; tama[j].y2=y; tama[j].Size=3; tama[j].attack=1; break; } } }
else if(Waza select==1 && Key[ KEY INPUT SPACE ] == 1 ){ //・・・(3) tama counter=5; shot=tamaMAX; : : } tama Draw(); }
void tama Draw(void)
{
for(j=0; j<shot; j++){ if(tama[j].flag==1){ tama[j].y-=7;
DrawRotaGraph(tama[j].x , tama[j].y, 1.5f, 0, cut[0], TRUE); tama Hantei();
}
¨ §
¥ ¦ void tama Input(void)
この関数の (1) の if 文では玉を発射する時間を計測している。シューティングゲームではある一定 の感覚で玉が発射するようになっており、この if 文では玉を一定の間隔で発射する仕組みである。 ¨ § ¥ ¦ int tama counter
で玉を発射する間隔を調整して、玉を発射したらこれを0にし、そうでなけでばカウンターの数値 を増加する。(2) の else if 文ではカウンターが一定まで貯まり、このときに Waza select==0 かつ
SPACEキーを押したときに発射する玉の位置や大きさなどのデータを読み込むものである。玉は 画面上に多く出したいので、玉1つ1つのデータを読み込む必要があるため、玉のデータは構造体 と配列で読み込むようにしている。また、(3) のように tama counter=5 にすることで (2) とは違 い、間隔をあけずに玉を発射するようになっている。これにより、選択した技によって玉の間隔を 狭めたり広めたりすることが可能となる。読み込んだデータは関数 tama Draw(void) で玉を画面 上に表示し、それを動かしている。 また、シューティングゲームを作る際に、攻撃のバリエーションはできるだけ多い方良いのでは と思い、このゲームでは4つの攻撃方法で敵を倒すゲームにしました。この理由としては戦略が 広がり、楽しみも増えるのではと思い、選択した技によって違う攻撃ができるようにし、それを SPACEキーで発射するというようにしました。この方法としては ¨ § ¥ ¦ Waza select という変数を用意し、選択した技に合わせて、SPACE キーを押すことによりそれに対応する技を発 射することができる。例えば Waza select==0 なら普通に玉を発射するのですが、Waza select==2 を選択すれば火炎放射のようなもので敵を倒すようになり、 Waza select==3 だと自機の周りに 弾を発射するようになっている。 関数 tama Draw(void) 内にある ¨ § ¥ ¦ DrawRotaGraph(tama[j].x , tama[j].y, 1.5f, 0, cut[0], TRUE);
は画面上に表示する玉を描くコマンドである。
cut[0]は私が書いた自機が発射する玉であり、1.5f で cut[0] の大きさを 1.5 倍にしている。tama[j].x
と tama[j].y は先ほど読み込んだ玉のデータの x 軸と y 軸の位置を表している。この上にある
tama[j].y-=7 で玉は 7 づつ画面上にあがっていくようにしている。関数 tama Hantei(); で当たっ
た時の判定を計算して、敵を倒して点数を稼いだりするなどを行っている。
また、このプログラムは玉を発射するときにだけでなく、攻めてきた敵や敵が発射したときの玉 を画面上に表示するときも上記のプログラムを自分なりに応用して使用している。
6
データを表示するプログラム
ゲームにおいて最も重要なことは、図 8 のように画面上に残りの体力や制限時間を表示すること である。これを画面右端に表示することで、現在の状況がどんな感じなのかが、すぐ分かるように なっている。ここに書いてあるのはスコア、残り時間、体力などゲームを進めていく上で必要不可 欠なものや、初期設定や操作方法を書くことにより、いつでも操作を確認できるため、誰でも気軽 にゲームができるように工夫している。 図 8: ゲームのデータ6.1
HPバーの表示
ゲームをやっているとよく図 9 のようなHP(ヒットポイント)などのバーであるが、このよう なものも DX ライブラリで表現することができる。私のゲームでは自機と敵両方にHPバーをつ け、体力が見やすいように体力が20%以下になったら赤になり、50%以下なら黄色、それ以外 なら自機なら黄緑色、敵なら青色というようにしている。下記のプログラムは自機のHPバーを表 示するプログラムである。 図 9: HPバーDrawStringToHandle( 505 , 68 , ”HP” , GetColor( 255 , 255 , 0 ) , FontHGS20) ;
DrawFormatStringToHandle( 545 , 80 , GetColor( 255 , 255 , 255 ) , FontHG20, ”% d”, hp); DrawFormatStringToHandle( 575 , 80 , GetColor( 255 , 255 , 255 ) , FontHG20, ”/% d”, Maxhp) ; DrawBox(525, 70, 635, 80, GetColor(0, 0, 0), TRUE);
if(hp<20)
DrawBox(525, 70,525+110*hp/Maxhp, 80, GetColor(255, 0, 0), TRUE ); else if(hp<50)
DrawBox(525, 70, 525+110*hp/Maxhp, 80, GetColor(255, 255, 0), TRUE ); else
DrawBox(525, 70, 525+110*hp/Maxhp, 80, GetColor(0, 225, 0), TRUE ); DrawBox(525, 70, 635, 80, GetColor(255, 255, 255), FALSE);
まず、図 9 のように文字を表示することから始める。残りHPなどの文字は「文字の大きさ20の HG
行書体」で表したいので int 型で宣言した FontHG20 を”DrawStringToHandle”と”DrawFormatStringToHandle” の括弧内に入れる。次にHPバーであるがここは”DrawBox”というコマンドで四角形を書くこと により、ゲームでよく拝見するHPバーを再現できる。括弧内の4つの数字は四角形の左上の座 標と右下の座標であり、”GetColor()”により四角形に色をつけることが可能である。また、括弧内 の”TRUE”は四角形の中を塗りつぶすという意味である。 ¨ § ¥ ¦ DrawBox(525, 70, 525+110*hp/Maxhp, 80, GetColor(0, 225, 0), TRUE );
これはHPバーの中にある四角形である。この場合は黄緑色で塗りつぶした左上の座標が (525,70)、右 下の座標 (525+110*hp/Maxhp,80) の四角形を書いていることがわかる。括弧内の”525+110*hp/Maxhp” の”hp”は現在の残りの体力、”Maxhp”は体力の最大値を表している。この”hp”が0以下になった 時にゲームが終了するようになっている。一般的なゲームではダメージを受けるとHPが減ると同 時にHPバーの色の部分が短くなるしくみであるが、それを再現するために体力の最大値に対して 残りの体力がどのくらいの割合があるのかで、バーの長さが変化するようにした。例えば図 9 は体 力の最大値が100なのに対して残りの体力が65なので、バーの長さを最大の65%にすればよ い。さらに、if 文を使い分けることで体力の変化により、四角形の色を変化させることが可能であ る。また、バーを白色の四角形で囲むことで、よりバーが見やすくなり、この方法としては、先ほ どのプログラムの後に ¨ § ¥ ¦ DrawBox(525, 70, 635, 80, GetColor(255, 255, 255), FALSE);
と書けばよい。括弧内の”FALSE”は”GetColor()”に指定した色で四角形を書くことができる。こ れにより、先ほど書いたプログラムを囲むことができるので、体力の最大値からどのくらいダメー ジを受けているのかが分かるようになった。もし、これを先ほどのプログラムの前に書いてしまう と、この四角形が上書きされるためこれが隠れてしまう。敵のHPも同様のように書いているため 敵のHPは省略する。
6.2
技の選択画面
このプログラムは図 10 を書いてあるもので、これはどの技があとどのくらい使えるかというの を表している。このゲームは技が4つあり、技には通常攻撃という無限に打てる技と、ある程度制 限されている特殊技があり、これを状況に合わせて技を使い分けてスコアを伸ばしていくゲームで ある。上記のプログラムの前半はカーソルの操作であり、後半は図 10 を描くプログラムである。
DrawBox(502, (Waza x+20)+Waza select*20, 635, (Waza x+40)+Waza select*20 , GetColor(255, 255, 0), FALSE);
if(Waza counter<10) Waza counter++;
else if(Key[ KEY INPUT Z ] == 1 ){ Waza counter=0;
Waza select++; if(Waza select>3) Waza select=0;
}
else if(Key[ KEY INPUT X ] == 1 ){ Waza select=0;
}
DrawStringToHandle(535, 370, ”攻撃技”, GetColor(255, 255, 0) , FontHG20) ;
DrawGraph(505 ,390, Waza1[0], TRUE); for(a=1; a<WazaX; a++){
DrawGraph(505 ,390+20*a, Waza1[a], TRUE);
DrawBox(530, 395+20*a, 630, 405+20*a, GetColor(0, 0, 0), TRUE); DrawBox(530, 395+20*a, 530+100*Waza[a]/MaxWaza[a], 405+20*a , GetColor(255, 0, 255), TRUE );
DrawBox(530, 395+20*a, 630, 405+20*a, GetColor(255, 255, 255) , FALSE); } 図 10: 技選択画面 技の選択方法は図 8 の操作方法に書いてあるように、Z,X キーを入力し、カーソルを動かすことで それに合った技を選択できる。このとき、ある程度時間の間隔をあけることで、ゆっくり技の選択 ができるようになる。このカウンターが溜まったら、Z キーを押すことによりカーソルが下に移動
し技の選択ができ、C キーでカーソルを 1 番上に持ってくることができる。 プログラムの後半部分では図 10 の部分を書いており、ここのバーも先ほどと同じく、どのくらい 技を使ったのかが分かるようになっている。図 10 ではカーソルで選択している技が使えないよう になっている。for 文の中は分割した技のイメージ画像とその技のバーである。画像と技の種類を 配列にすることで for 文が使えるようになり、プログラムを書く上ではるかに短縮することができ る。そのため、バーが必要でない通常攻撃を for 文の前に書き、バーが必要な特殊技を for 文の中 で書くことでプログラムの短縮が可能だ。
7
まとめ
今回の卒業研究で、初めて本格的なゲームの開発をやりましたが、ゲーム開発に必要な数学や物 理の知識が不足していたのではないかと思っています。しかし、反省点としては、キャラクター設 定などギリギリの時間で作っていたため、キャラクターごとの必殺技などが作れなくて残念に思っ ている。DXライブラリで表現できる部分が多いため、3Dゲームや通信機能も表現できることか ら、今後はもっと納得のいくゲームやアプリケーションを作っていきたいと思います。DXライブ ラリでゲームを作る場合、計算機基礎実習 ・ 程度の知識があればゲームの開発ができるので、 もっと早い段階DXライブラリの存在を知り、ゲーム開発に取り組んでおけばよかったと思ってい ます。8
参考文献
参考文献
[1] C言語なんでも質問サイト http://dixq.net/ [2] DXライブラリ置場 http://homepage2.nifty.com/natupaji/DxLib/ [3] Microsoft Visual Studio 2008 Express Editionhttp://www.microsoft.com/japan/msdn/vstudio/express/
[4] フォント一覧
http://homepage2.nifty.com/yoshi-m/makehp/other/font.htm
[5] プログラミング・演習のページ
9
プログラム
#include "DxLib.h" #include <math.h> #define tamaMAX 500 #define StarMAX 30 #define SparkMAX 800 #define tekiMAX 100 #define WazaX 4 #define charaX 9 #define PI 3.14159265358979323846 //円周率 char Key[256]; struct shot{ double x, y, x2, y2; double angle, X, Y, Z; int color; int flag; int Size; int attack; int A,d; }; struct STAR{ int x, y; int flag; int apple;int hantei_x, hantei_y, range; int color; int Size; int hp, Maxhp; int mp1, mp2; int attack; int counter; int shot; int teki; }; struct TEKI{ double x, y; int i;
int flag; int Size; int attack; double angle; int counter, shot; int color;
int hantei_x, hantei_y, range; }; struct SPARK{ int x, y, xflag; int flag; int Sx, Sy; int color; int i, star; int time; }; void StartData(void); void GameStart(void); void GameOver(void); void Fighter(void); void Star_Draw(void); void Star_Hantei(void); void teki_Attack(void); void teki_Draw(void); void teki_Hantei(void);
void CreateSpark(int m, int n); void MoveSpark(void);
void tama_Input(void); void tama_Draw(void); void tama_Hantei(void); void GameDate(void);
struct shot tama[tamaMAX]; struct STAR Star[StarMAX]; struct TEKI teki[tekiMAX]; struct SPARK Spark[SparkMAX];
int tama_counter=50, Star_counter=20; int a, i, j, k, m, n;
int x, y; int P, Q;
int shot, teki_shot; int hp, Maxhp;
int attack, speed, count; int score=0, score1, hscore=0;
int tekiHP=1, tekiMAXHP=1, tekiHPflag=0; int food_count=0;
double hantei_x, hantei_y, range1; double hantei_X, hantei_Y, range2; int Haikei_X=-960; int teki_count=0; int RefreshTime; // 1 2 3 4 5 6 7 8 9 int Maxhp0[10]= {3, 3, 5, 2, 3, 2, 5, 4, 3}; int attack0[10]={3, 1, 5, 1, 3, 4, 3, 2, 4}; int speed0[10]= {3, 3, 1, 5, 4, 2, 2, 4, 2}; int count0[10]= {3, 5, 1, 4, 2, 4, 2, 2, 3};
int start_select=0, start_counter=0;
int time, time1, time_select=2, time_counter=0;
int Lv=3, Lv_select=2, Lv_counter=0, LvA=0, LvC=0, LvHP=0, LvS=0, Lv_color; int character=0, character_counter=0;
char Lv1[10];
int end_select=0, end_counter=0;
int Waza[WazaX], MaxWaza[WazaX];
int Waza_select=0, Waza_counter=0, Waza_x=370;
int Haikei, Open, Data, end; int MAN[32];
int cut[10];
int Apple[32], Mp[5];
int teki0[10], teki1[10], teki2, teki3, teki4, teki5; int tekiA;
int Waza0, Waza1[10]; int Bom,Block,Allow;
int FontHGS_Spark, FontHGS16, FontHGS20, FontHGS24, FontHG16, FontHG20, FontHG24, FontHG48;
//---ここから WinMain 文
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
if( DxLib_Init() == -1 ) return -1; //DXライブラリ初期化 が起きたら終了 SetMainWindowText( "天空の牧場" ) ; FontHGS_Spark=CreateFontToHandle("HGS創英角ポップ体", 10, 1, DX_FONTTYPE_NORMAL); FontHGS20=CreateFontToHandle("HGS創英角ポップ体", 16, 16, DX_FONTTYPE_NORMAL); FontHGS24=CreateFontToHandle("HGS創英角ポップ体", 24, 6, DX_FONTTYPE_NORMAL); FontHG16=CreateFontToHandle("HG行書体", 16, 1, DX_FONTTYPE_NORMAL); FontHG20=CreateFontToHandle("HG行書体", 20, 1, DX_FONTTYPE_NORMAL); FontHG24=CreateFontToHandle("HG行書体", 24, 1, DX_FONTTYPE_NORMAL); FontHG48=CreateFontToHandle("HG行書体", 48, 1, DX_FONTTYPE_NORMAL); Open=LoadGraph("PicFile\\Open.png"); end=LoadGraph("PicFile\\end.png"); LoadDivGraph("PicFile\\man.png", 18, 2, 9, 16, 16, MAN); Haikei=LoadGraph("PicFile\\haikei.png"); Data=LoadGraph("PicFile\\data.png"); LoadDivGraph("PicFile\\Apple.png", 16, 4, 4, 20, 20, Apple); LoadDivGraph("PicFile\\mp.png", 4, 2, 2, 20, 20, Mp); LoadDivGraph("PicFile\\teki0.png", 6, 3, 2, 20, 17, teki0); LoadDivGraph("PicFile\\teki1.png", 2, 2, 1, 16, 16, teki1); teki2=LoadGraph("PicFile\\teki2.png"); teki3=LoadGraph("PicFile\\teki3.png"); teki4=LoadGraph("PicFile\\teki4.png"); teki5=LoadGraph("PicFile\\teki5.png"); Bom=LoadGraph("PicFile\\Bom.png"); Block=LoadGraph("PicFile\\Block.png"); Allow=LoadGraph("PicFile\\Allow.png"); LoadDivGraph("PicFile\\Waza.png", 4, 2, 2, 20, 20, Waza1); LoadDivGraph("PicFile\\cut.png", 2, 2, 1, 9, 9, cut); SetDrawScreen( DX_SCREEN_BACK ) ;//描画先を裏画面に設定 GameStart(); StartData(); while(1){ RefreshTime = GetNowCount(); //今の時間を取得 ClearDrawScreen(); //裏画面のデータを全て削除 GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る
if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 DrawGraph(500, 0, Data, FALSE);
DrawGraph(0, Haikei_X, Haikei, FALSE); Haikei_X+=1; if(Haikei_X>0) Haikei_X=-960; Fighter(); //戦闘機のデータ MoveSpark(); Star_Draw(); //隕石のデータ tama_Input(); //戦闘機のレーザー GameDate(); //ESCが押されたらブレイク
if( Key[ KEY_INPUT_ESCAPE] == 1 ) { hp=0; GameOver(); } ScreenFlip() ;//裏画面データを表画面へ反映 while(GetNowCount() - RefreshTime < 11); } DxLib_End() ;// DXライブラリ使用の終了処理 return 0 ;// ソフトの終了 } //---ここまで WinMain 文 void StartData(void) { hp=Maxhp; if(score1>hscore) hscore=score1; score=0; time=time1*60+60; //時間設定 tekiHP=1; tekiMAXHP=1; tekiHPflag=0;
food_count=0; teki_count=0; x=250; y=420; if(Lv==4){ LvHP=1; LvA=1; } if(Lv==5){ LvHP=2; LvA=3; LvS=2; } if(Lv==6){; LvC=1; LvS=10; } Maxhp=Maxhp0[character]*20+40; hp=Maxhp; attack=attack0[character]; speed=speed0[character]+1; count=count0[character]-1; Haikei_X=-960; Waza_select=0; Waza_counter=0;
for(i=1; i<WazaX; i++){ MaxWaza[i]=1000;
Waza[i]=MaxWaza[i]*0.3; }
for(i=0; i<tamaMAX; i++){ tama[i].y=450;
tama[i].flag=0; }
for(i=0; i<tekiMAX; i++){ teki[i].flag=0; teki[i].y=0;
teki[i].counter=0; }
for(i=0; i<StarMAX; i++){ Star[i].flag=0; Star[i].y=0;
}
for(i=0; i<SparkMAX; i++){ Spark[i].flag=0; Spark[i].xflag=0; } } // ゲームスタート処理 void GameStart(void) { while(1){ GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る
if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 DrawGraph(0, 0, Open, FALSE);
DrawStringToHandle( 100 , 80 , "天 空 の 牧 場" , GetColor(255, 255, 0) , FontHG48);
DrawBox(195, 250+25*start_select, 490, 275+25*start_select, GetColor(255, 255, 0), FALSE); DrawStringToHandle( 250 , 250 , "Game Start" , GetColor(255, 255, 255) , FontHGS24);
DrawGraph(200, 252, teki0[0], TRUE);
DrawFormatStringToHandle(250 , 275 , GetColor(255, 255, 255) , FontHGS24, "Time : %d" ,time1);
DrawGraph(200, 277, Apple[0], TRUE);
DrawStringToHandle( 250 , 300 , "難易度 : " , GetColor(255, 255, 255) , FontHGS24); DrawFormatStringToHandle(360 , 300 ,Lv_color , FontHG24, "%s " ,Lv1);
DrawGraph(200, 302, Apple[13], TRUE);
DrawStringToHandle( 250 , 325 , "キャラクター:" , GetColor(255, 255, 255) , FontHGS24); DrawGraph(200, 327, teki4, TRUE);
DrawGraph(430, 327, MAN[character*2], TRUE);
if(start_counter<10) start_counter++;
else if(Key[ KEY_INPUT_DOWN ] == 1 ){ start_counter=0;
start_select++; if(start_select>3)
start_select=3; }
else if(Key[ KEY_INPUT_UP ] == 1 ){ start_counter=0; start_select--; if(start_select<0) start_select=0; } if(time_counter<10) time_counter++;
else if(start_select==1 && Key[ KEY_INPUT_RIGHT ] == 1 ){ time_counter=0;
time_select++; if(time_select>7)
time_select=7; }
else if(start_select==1 && Key[ KEY_INPUT_LEFT ] == 1 ){ time_counter=0; time_select--; if(time_select<1) time_select=1; } if(time_select==0) time1=10; else if(time_select==1) time1=30; else if(time_select==2) time1=60; else if(time_select==3) time1=90; else if(time_select==4) time1=120; else if(time_select==5) time1=180; else if(time_select==6) time1=300; else if(time_select==7) time1=600;
if(Lv_counter<10) Lv_counter++;
else if(start_select==2 && Key[ KEY_INPUT_RIGHT ] == 1 ){ Lv_counter=0;
Lv++; if(Lv>6)
Lv=6; }
else if(start_select==2 && Key[ KEY_INPUT_LEFT ] == 1 ){ Lv_counter=0; Lv--; if(Lv<1) Lv=1; } if(Lv==1){ strcpy(Lv1,"Very Easy"); Lv_color=GetColor(0, 255, 255); } if(Lv==2){ strcpy(Lv1,"Easy"); Lv_color=GetColor(0, 255, 0); } if(Lv==3){ strcpy(Lv1,"Normal"); Lv_color=GetColor(255, 255, 0); } if(Lv==4){ strcpy(Lv1,"Hard"); Lv_color=GetColor(255, 128, 0); } if(Lv==5){ strcpy(Lv1,"Very Hard"); Lv_color=GetColor(255, 0, 0); } if(Lv==6){ strcpy(Lv1,"High Speed"); Lv_color=GetColor(255, 100, 255); } if(character_counter<10) character_counter++;
else if(start_select==3 && Key[ KEY_INPUT_RIGHT ] == 1 ){ character_counter=0;
character++;
if(character>charaX-1) character=charaX-1; }
else if(start_select==3 && Key[ KEY_INPUT_LEFT ] == 1 ){ character_counter=0;
character--; if(character<0)
character=0; }
DrawBox(105, 370, 535, 470, GetColor(0, 255, 0), FALSE); if(start_select==0)
DrawStringToHandle(110, 375, "スペースキーを押すとゲームが始まります" , GetColor(255, 255, 255), FontHGS20);
else if(start_select==1)
DrawStringToHandle(110, 375, "制限時間の設定ができます", GetColor(255, 255, 255), FontHGS20); else if(start_select==2 && Lv==1)
DrawStringToHandle(110, 375, "練習用です" , GetColor(255, 255, 255) , FontHGS20); else if(start_select==2 && Lv==2)
DrawStringToHandle(110, 375, "やや簡単で、強敵は出てきません" , GetColor(255, 255, 255), FontHGS20);
else if(start_select==2 && Lv==3)
DrawStringToHandle(110, 375, "普通のレベルです" , GetColor(255, 255, 255) , FontHGS20); else if(start_select==2 && Lv==4)
DrawStringToHandle(110, 375, "敵が強くなっています" , GetColor(255, 255, 255) , FontHGS20); else if(start_select==2 && Lv==5)
DrawStringToHandle(110, 375, "敵が早く、敵もパワーアップしています", GetColor(255, 255, 255) , FontHGS20);
else if(start_select==2 && Lv==6)
DrawStringToHandle(110, 375, "敵がものすごく速いです" , GetColor(255, 255, 255) , FontHGS20); else if(start_select==3){
DrawStringToHandle(110, 375, "HP :" , GetColor(0, 200, 255) , FontHGS20); DrawStringToHandle(110, 400, "パワー :" , GetColor(255, 0, 50) , FontHGS20); DrawStringToHandle(110, 425, "スピード:" , GetColor(255, 255, 0) , FontHGS20); DrawStringToHandle(110, 450, "カウント:" , GetColor(0, 255, 0) , FontHGS20); for(i=0; i<Maxhp0[character]; i++)
DrawStringToHandle(200+20*i, 375, "★" , GetColor(0, 200, 255) , FontHGS20); for(j=0; j<attack0[character]; j++)
DrawStringToHandle(200+20*j, 400, "★" , GetColor(255, 0, 50) , FontHGS20); for(k=0; k<speed0[character]; k++)
DrawStringToHandle(200+20*k, 425, "★" , GetColor(255, 255, 0) , FontHGS20); for(m=0; m<count0[character]; m++)
DrawStringToHandle(200+20*m, 450, "★" , GetColor(0, 255, 0) , FontHGS20); }
ScreenFlip();
if(Key[ KEY_INPUT_RETURN] == 1 || (start_select==0 && Key[ KEY_INPUT_SPACE] == 1 )) break; } } // ゲームオーバー処理 void GameOver(void) { int Gyou=150; while(1){ GetHitKeyStateAll( Key ) ; // すべてのキーの状態を得る
if( ProcessMessage() == -1 ) break ; //異常がおきたら終了 DrawGraph(500, 0, Data, FALSE);
DrawGraph(0, Haikei_X, Haikei, FALSE);
if(hp<1)
DrawStringToHandle( 80 , 100 , "GAME OVER" , GetColor(255, 0, 0) , FontHG48);
DrawFormatStringToHandle(100 , Gyou , GetColor(0, 255, 255) , FontHG24, "Score %d", score);
DrawFormatStringToHandle(100 , Gyou+25 , GetColor(255, 50, 255) , FontHG24, "ボーナスポイント %d", Lv*time1*5 + food_count*100);
DrawFormatStringToHandle(120, Gyou+45, GetColor(255, 255, 255) , FontHG20, "難易度 %d×%d = %d", Lv, time1*5, Lv*time1*5) ;
DrawFormatStringToHandle(120, Gyou+65, GetColor(255 ,255, 255) , FontHG20, "食べ物 %d× 100 = %d", food_count, food_count*100);
score1= score + Lv*time1*5 + food_count*100;
DrawLine(100, Gyou+85, 400, Gyou+85, GetColor(255,255,255), FALSE);
DrawFormatStringToHandle(100 , Gyou+85 , GetColor(255 ,255 ,0) , FontHG24, "
DrawStringToHandle( 100 , 300 , "Continue?" , GetColor(255, 255, 255) , FontHG24); DrawStringToHandle( 120 , 325 , "・Yes" , GetColor(255, 255, 255) , FontHG24);
DrawStringToHandle( 220 , 325 , "・No" , GetColor(255, 255, 255), FontHG24);
DrawBox(120+100*end_select, 325, 200+100*end_select, 350, GetColor(255, 255, 0), FALSE); if(end_counter<10)
end_counter++;
else if(Key[ KEY_INPUT_RIGHT ] == 1 ){ end_counter=0;
end_select++; if(end_select>1)
end_select=0; }
else if(Key[ KEY_INPUT_LEFT ] == 1 ){ end_counter=0;
end_select--; if(end_select<0)
end_select=1; }
if(end_select==0 && Key[ KEY_INPUT_RETURN ] == 1 ){ StartData();
break; }
else if(end_select==1 && Key[ KEY_INPUT_RETURN ] == 1 ){
DxLib_End(); // DXライブラリ終了 exit( -1 ); // ソフト終了 } ScreenFlip(); } } // 戦闘機のデータ void Fighter(void) { //戦闘機を書く
DrawRotaGraph(x+16, y+16, 2.0f, 0, MAN[character*2], TRUE); //戦闘機の操作
if( Key[ KEY_INPUT_RIGHT ] == 1 ) x+=speed;
if(x>=468) x=468;
x-=speed; if(x<=0)
x=0;
if( Key[ KEY_INPUT_DOWN ] == 1 ) y+=speed;
if(y>448) y=448;
if( Key[ KEY_INPUT_UP ] == 1 ) y-=speed; if(y<150) y=150; } //隕石のデータ void Star_Draw(void) { if(Star_counter<30 - 25*LvC) Star_counter++; else{ Star_counter=0;
for(i=0; i<StarMAX; i++){ if(Star[i].flag==0){ Star[i].x=10*GetRand(47)+10; P=GetRand(1000); //敵出現 if(P<100){ if(Lv==1) Star[i].flag=0; else Star[i].flag=2; Star[i].hp=15; Star[i].Maxhp=Star[i].hp; } else if(P<150){ if(Lv==1 || Lv==6) Star[i].flag=0; else Star[i].flag=3; Star[i].hp=5; Star[i].Maxhp=Star[i].hp;
} //中ボス出現 else if(P<200){ if(Lv<3 || Lv>5) Star[i].flag=0; else Star[i].flag=GetRand(5)+4; if(teki_count<1) teki_count++; else Star[i].flag=0; Star[i].x=30*(GetRand(8)+1); //中ボスフラグ if(Star[i].flag==4) Star[i].hp=50+LvHP*5; else if(Star[i].flag==5) Star[i].hp=60+LvHP*5; else if(Star[i].flag==6) Star[i].hp=40+LvHP*5; else if(Star[i].flag==7) Star[i].hp=55+LvHP*5; else if(Star[i].flag==8) Star[i].hp=42+LvHP*5; else if(Star[i].flag==9) Star[i].hp=50+LvHP*5; Star[i].Maxhp=Star[i].hp; } //アイテム出現 else if(P<350){ Star[i].flag=10; Star[i].apple=GetRand(15); } else if(P<450){ Star[i].flag=11; Star[i].mp1=GetRand(29); if(Star[i].mp1==0) Star[i].mp2=2; else if(Star[i].mp1<5) Star[i].mp2=3; else if(Star[i].mp1<15) Star[i].mp2=1;
else Star[i].mp2=0; } else{ Star[i].flag=1; if(Lv==6) Star[i].hp=2; else Star[i].hp=10; Star[i].Maxhp=Star[i].hp; Star[i].color=GetRand(5); } break; } } }
for(i=0; i<StarMAX; i++){ //敵出現
if(Star[i].flag==1){ Star[i].Size=10; Star[i].y+=1+LvS;
DrawGraph(Star[i].x , Star[i].y, teki0[Star[i].color], TRUE); Star_Hantei();
}
else if(Star[i].flag==2){ Star[i].y+=1+LvS; Star[i].Size=10;
DrawGraph(Star[i].x , Star[i].y, Bom, TRUE); Star_Hantei();
}
else if(Star[i].flag==3){ Star[i].y+=5+LvS; Star[i].Size=5;
DrawGraph(Star[i].x , Star[i].y, Allow, TRUE); Star_Hantei();
}
else if(Star[i].flag==4){
if(Star[i].y>100 && Star[i].hp>0) Star[i].y=100;
else
Star[i].y+=1; Star[i].Size=20;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki1[0], TRUE); Star_Hantei();
teki_Attack(); }
else if(Star[i].flag==5){
if(Star[i].y>100 && Star[i].hp>0) Star[i].y=100;
else
Star[i].y+=1; Star[i].Size=20;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki1[1], TRUE); Star_Hantei();
teki_Attack(); }
else if(Star[i].flag==6){
if(Star[i].y>50 && Star[i].hp>0) Star[i].y=50;
else
Star[i].y+=2; Star[i].Size=20; Star[i].x=x;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki2, TRUE); Star_Hantei();
teki_Attack(); }
else if(Star[i].flag==7){
if(Star[i].y>100 && Star[i].hp>0) Star[i].y=100;
else
Star[i].y+=1; Star[i].Size=20;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki3, TRUE); Star_Hantei();
teki_Attack(); }
else if(Star[i].flag==8){
if(Star[i].y>30 && Star[i].hp>0) Star[i].y=30;
else
Star[i].Size=20;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki4, TRUE); Star_Hantei();
teki_Attack(); }
else if(Star[i].flag==9){
if(Star[i].y>100 && Star[i].hp>0) Star[i].y=100;
else
Star[i].y+=1; Star[i].Size=20;
DrawRotaGraph(Star[i].x+20 , Star[i].y+20, 2.0f, 0, teki5, TRUE); Star_Hantei(); teki_Attack(); } //アイテム出現 else if(Star[i].flag==10){ Star[i].y+=1+LvS; Star[i].Size=10;
DrawGraph(Star[i].x , Star[i].y, Apple[Star[i].apple], TRUE); Star_Hantei();
}
else if(Star[i].flag==11){ Star[i].y+=1+LvS;
Star[i].Size=10;
DrawGraph(Star[i].x , Star[i].y, Mp[Star[i].mp2], TRUE); Star_Hantei(); } } } //隕石の判定 void Star_Hantei(void) { if(Star[i].y>480){ Star[i].x=10*GetRand(47)+10; Star[i].y=0; Star[i].flag=0; } //隕石に当たった時のダメージ判定
hantei_x=(x+8)-Star[i].x; hantei_y=(y+8)-Star[i].y; range1=16+Star[i].Size;
if( hantei_x * hantei_x + hantei_y * hantei_y < range1 * range1){ if(Star[i].flag==1){
hp-=10;
DrawRotaGraph(x+16, y+16, 2.0f, 0, MAN[character*2+1], TRUE); }
else if(Star[i].flag==2){ hp-=20;
DrawRotaGraph(x+16, y+16, 2.0f, 0, MAN[character*2+1], TRUE); for(int k=0; k<10; k++){ CreateSpark(x+16, y+16); } } else if(Star[i].flag==3){ hp-=10;
DrawRotaGraph(x+16, y+16, 2.0f, 0, MAN[character*2+1], TRUE); } else if(Star[i].flag==10){ if(Star[i].apple>9) hp+=10; else hp+=5; food_count++; } else if(Star[i].flag==11){ if(Star[i].mp2==3){
for(a=1; a<WazaX; a++) Waza[a]-=MaxWaza[a]*0.2; }
else if(Star[i].mp2==2){ for(a=1; a<WazaX; a++)
Waza[a]+=MaxWaza[a]*0.3; }
else if(Star[i].mp2==1){ for(a=1; a<WazaX; a++)
Waza[a]+=MaxWaza[a]*0.1; }
else{
for(a=1; a<WazaX; a++) Waza[a]+=MaxWaza[a]*0.05;
} } Star[i].y=490; Star[i].flag=0; if(hp>=Maxhp) hp=Maxhp; if(hp<=0){ hp=0; GameOver(); }
for(a=1; a<WazaX; a++){ if(Waza[a]>=MaxWaza[a]) Waza[a]=MaxWaza[a]; if(Waza[a]<1) Waza[a]=0; } } } //敵の玉設定 void teki_Attack(void) { if(teki[i].counter<30) teki[i].counter++; else if(Star[i].flag==4){ teki[i].counter=10; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=1; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=4; teki[teki[i].i].attack=3+LvA; teki[teki[i].i].angle=PI/3+(PI*GetRand(30)/90); break; } }
}
else if(Star[i].flag==5){ teki[i].counter=20; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=2; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=3; teki[teki[i].i].attack=2+LvA*2; teki[teki[i].i].angle=PI/4+(PI*GetRand(30)/90); break; } } } else if(Star[i].flag==6){ teki[i].counter=0; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=3; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=3; teki[teki[i].i].attack=3+LvA; break; } } } else if(Star[i].flag==7){ teki[i].counter=25; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=4; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=4; teki[teki[i].i].attack=3+LvA; teki[teki[i].i].color=255; teki[teki[i].i].angle=m*PI/6; m++;
break; } } } else if(Star[i].flag==8){ teki[i].counter=25+LvA; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=5; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=2; teki[teki[i].i].attack=1; teki[teki[i].i].color=255; teki[teki[i].i].angle=PI/6+(PI*GetRand(120)/180); break; } } } else if(Star[i].flag==9){ teki[i].counter=25+LvA; teki[i].shot=tekiMAX;
for(teki[i].i=0; teki[i].i<teki[i].shot; teki[i].i++){ if(teki[teki[i].i].flag==0){ teki[teki[i].i].flag=6; teki[teki[i].i].x=Star[i].x+20; teki[teki[i].i].y=Star[i].y+20; teki[teki[i].i].Size=2; teki[teki[i].i].attack=1; teki[teki[i].i].angle=PI/3+(PI*GetRand(30)/90); break; } } } teki_Draw(); } //敵の玉を描く void teki_Draw(void) {
if(teki[teki[i].i].flag==1){
teki[teki[i].i].x+=cos(teki[teki[i].i].angle)*4; teki[teki[i].i].y+=sin(teki[teki[i].i].angle)*4;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(0,50,0) ,TRUE ); teki_Hantei(); } else if(teki[teki[i].i].flag==2){ teki[teki[i].i].x+=cos(teki[teki[i].i].angle)*6; teki[teki[i].i].y+=sin(teki[teki[i].i].angle)*6;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(128,0,255) ,TRUE );
teki_Hantei(); }
else if(teki[teki[i].i].flag==3){ teki[teki[i].i].y+=5;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(255,255,0) ,TRUE ); teki_Hantei(); } else if(teki[teki[i].i].flag==4){ teki[teki[i].i].x+=cos(teki[teki[i].i].angle)*2; teki[teki[i].i].y+=sin(teki[teki[i].i].angle)*2; teki[teki[i].i].color-=3; if(teki[teki[i].i].color<0) teki[teki[i].i].color=0;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(255,teki[teki[i].i].color,0) ,TRUE ); teki_Hantei(); } else if(teki[teki[i].i].flag==5){ teki[teki[i].i].x+=cos(teki[teki[i].i].angle)*2; teki[teki[i].i].y+=sin(teki[teki[i].i].angle)*2; teki[teki[i].i].color--; if(teki[teki[i].i].color<0) teki[teki[i].i].color=0;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(0,teki[teki[i].i].color,255) ,TRUE );
teki_Hantei(); }
if(teki[teki[i].i].flag==6){
teki[teki[i].i].y+=sin(teki[teki[i].i].angle)*7;
DrawCircle( teki[teki[i].i].x , teki[teki[i].i].y, teki[teki[i].i].Size, GetColor(0,0,0) , TRUE ); teki_Hantei(); } } } //敵の攻撃判定 void teki_Hantei(void) {
if(teki[teki[i].i].x<0 || teki[teki[i].i].x>490 || teki[teki[i].i].y<0 || teki[teki[i].i].y>500 || Star[i].hp<1){ teki[teki[i].i].y=500; teki[teki[i].i].flag=0; } teki[teki[i].i].hantei_x=(x+16)-teki[teki[i].i].x; teki[teki[i].i].hantei_y=(y+16)-teki[teki[i].i].y; teki[teki[i].i].range=16+teki[teki[i].i].Size;
if(teki[teki[i].i].hantei_x * teki[teki[i].i].hantei_x + teki[teki[i].i].hantei_y * teki[teki[i].i].hantei_y <= teki[teki[i].i].range * teki[teki[i].i].range){ hp-=teki[teki[i].i].attack;
DrawRotaGraph(x+16, y+16, 2.0f, 0, MAN[character*2+1], TRUE); teki[teki[i].i].y=500; teki[teki[i].i].flag=0; } if(hp<=0){ hp=0; GameOver(); } } //戦闘機のレーザー入力 void tama_Input(void) { if(tama_counter<25) tama_counter++;
tama_counter=count*5; shot=tamaMAX; for(j=0; j<shot; j++){ if(tama[j].flag==0){ tama[j].flag=1; tama[j].x=x+16; tama[j].y=y; tama[j].y2=y; tama[j].Size=3; tama[j].attack=attack; break; } } }
else if(Waza_select==1 && Key[ KEY_INPUT_SPACE ] == 1 ){ tama_counter=count*5; shot=tamaMAX; for(j=0; j<shot; j++){ if(Waza[Waza_select]<20){ break; } else if(tama[j].flag==0){ tama[j].flag=2; tama[j].x=x+16; tama[j].y=y; tama[j].y2=y; tama[j].Size=3; tama[j].attack=attack*2; Waza[Waza_select]-=20; break; } } }
else if(Waza_select==2 && Key[ KEY_INPUT_SPACE ] == 1 ){ tama_counter=25; shot=tamaMAX; for(j=0; j<shot; j++){ if(Waza[Waza_select]<2){ break; } else if(tama[j].flag==0){
tama[j].flag=3; tama[j].x=x+16; tama[j].y=y; tama[j].y2=y; tama[j].Size=5; tama[j].attack=1; tama[j].color=255; tama[j].angle=-(5*PI/12+(PI*GetRand(30)/180)); tama[j].d=GetRand(150)+50; Waza[Waza_select]-=2; break; } } }
else if(Waza_select==3 && Key[ KEY_INPUT_SPACE ] == 1 ){ tama_counter=25; shot=tamaMAX; for(j=0; j<shot; j++){ if(Waza[Waza_select]<2){ break; } else if(tama[j].flag==0){ tama[j].flag=4; tama[j].x=x+16; tama[j].x2=x+16; tama[j].y=y+16; tama[j].y2=y+16; tama[j].Size=2; tama[j].attack=5; tama[j].color=255; tama[j].angle=-n*PI/4; Waza[Waza_select]-=2; n++; break; } } } tama_Draw(); }
//戦闘機のレーザーを描く void tama_Draw(void) { for(j=0; j<shot; j++){ if(tama[j].flag==1){ tama[j].y-=6;
DrawRotaGraph(tama[j].x , tama[j].y, 1.5f, 0, cut[0], TRUE); tama_Hantei();
}
else if(tama[j].flag==2){ tama[j].y-=12;
DrawRotaGraph(tama[j].x , tama[j].y, 1.5f, 0, cut[1], TRUE); tama_Hantei(); } else if(tama[j].flag==3){ tama[j].x+=cos(tama[j].angle)*3; tama[j].y+=sin(tama[j].angle)*3; tama[j].color-=12; if(tama[j].color<0) tama[j].color=0;
DrawCircle( tama[j].x , tama[j].y, tama[j].Size, GetColor(255,tama[j].color,0) ,TRUE ); tama_Hantei(); } else if(tama[j].flag==4){ tama[j].x+=cos(tama[j].angle)*3; tama[j].y+=sin(tama[j].angle)*3; tama[j].color-=5; if(tama[j].color<0) tama[j].color=0;
DrawCircle( tama[j].x , tama[j].y, tama[j].Size, GetColor(0,tama[j].color,255) ,TRUE ); tama_Hantei(); } } } //レーザーの当たり判定 void tama_Hantei(void) {
for(i=0; i<StarMAX; i++){
Star[i].hantei_x=tama[j].x - (Star[i].x+Star[i].Size); Star[i].hantei_y=tama[j].y - (Star[i].y+Star[i].Size); Star[i].range=Star[i].Size + tama[j].Size;